@push.rocks/taskbuffer 3.1.10 โ 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/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/index.d.ts +4 -1
- package/dist_ts/index.js +3 -1
- package/dist_ts/taskbuffer.classes.task.d.ts +43 -9
- package/dist_ts/taskbuffer.classes.task.js +105 -1
- package/dist_ts/taskbuffer.classes.taskmanager.d.ts +23 -6
- package/dist_ts/taskbuffer.classes.taskmanager.js +98 -1
- package/dist_ts/taskbuffer.classes.taskstep.d.ts +27 -0
- package/dist_ts/taskbuffer.classes.taskstep.js +37 -0
- package/dist_ts/taskbuffer.interfaces.d.ts +36 -0
- package/dist_ts/taskbuffer.interfaces.js +2 -0
- package/dist_ts_web/00_commitinfo_data.d.ts +8 -0
- package/dist_ts_web/00_commitinfo_data.js +9 -0
- package/package.json +2 -2
- package/readme.md +414 -110
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +9 -1
- package/ts/taskbuffer.classes.task.ts +145 -18
- package/ts/taskbuffer.classes.taskmanager.ts +129 -9
- package/ts/taskbuffer.classes.taskstep.ts +57 -0
- package/ts/taskbuffer.interfaces.ts +39 -0
- package/ts_web/00_commitinfo_data.ts +8 -0
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,6 +70,42 @@ const myTask = new Task({
|
|
|
72
70
|
const result = await myTask.trigger();
|
|
73
71
|
```
|
|
74
72
|
|
|
73
|
+
### Task with Progress Steps ๐
|
|
74
|
+
|
|
75
|
+
Track granular progress for complex operations - perfect for UI progress bars:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
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
|
+
|
|
75
109
|
## TypeScript Generics Support ๐ฌ
|
|
76
110
|
|
|
77
111
|
TaskBuffer leverages TypeScript's powerful generics system for complete type safety across your task chains and workflows.
|
|
@@ -97,7 +131,7 @@ interface ProcessedUser {
|
|
|
97
131
|
}
|
|
98
132
|
|
|
99
133
|
// Create strongly typed tasks
|
|
100
|
-
const processUserTask = new Task<
|
|
134
|
+
const processUserTask = new Task<ProcessedUser>({
|
|
101
135
|
name: 'ProcessUser',
|
|
102
136
|
taskFunction: async (user: UserData): Promise<ProcessedUser> => {
|
|
103
137
|
return {
|
|
@@ -129,7 +163,7 @@ interface TaskConfig {
|
|
|
129
163
|
|
|
130
164
|
const configuredTask = new Task<TaskConfig>({
|
|
131
165
|
name: 'ConfiguredTask',
|
|
132
|
-
taskSetup: async () => ({
|
|
166
|
+
taskSetup: async (): Promise<TaskConfig> => ({
|
|
133
167
|
apiEndpoint: 'https://api.example.com',
|
|
134
168
|
retryCount: 3,
|
|
135
169
|
timeout: 5000
|
|
@@ -156,21 +190,21 @@ Chain tasks with preserved type flow:
|
|
|
156
190
|
|
|
157
191
|
```typescript
|
|
158
192
|
// Each task knows its input and output types
|
|
159
|
-
const fetchTask = new Task<void
|
|
193
|
+
const fetchTask = new Task<void>({
|
|
160
194
|
name: 'FetchUsers',
|
|
161
195
|
taskFunction: async (): Promise<UserData[]> => {
|
|
162
196
|
return await api.getUsers();
|
|
163
197
|
}
|
|
164
198
|
});
|
|
165
199
|
|
|
166
|
-
const filterTask = new Task<
|
|
200
|
+
const filterTask = new Task<void>({
|
|
167
201
|
name: 'FilterActive',
|
|
168
202
|
taskFunction: async (users: UserData[]): Promise<UserData[]> => {
|
|
169
203
|
return users.filter(user => user.isActive);
|
|
170
204
|
}
|
|
171
205
|
});
|
|
172
206
|
|
|
173
|
-
const mapTask = new Task<
|
|
207
|
+
const mapTask = new Task<void>({
|
|
174
208
|
name: 'MapToProcessed',
|
|
175
209
|
taskFunction: async (users: UserData[]): Promise<ProcessedUser[]> => {
|
|
176
210
|
return users.map(transformUser);
|
|
@@ -186,6 +220,204 @@ const chain = new Taskchain({
|
|
|
186
220
|
const finalResult: ProcessedUser[] = await chain.trigger();
|
|
187
221
|
```
|
|
188
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
|
+
|
|
189
421
|
## Buffer Behavior Deep Dive ๐
|
|
190
422
|
|
|
191
423
|
The buffer system in TaskBuffer provides intelligent control over concurrent executions, preventing system overload while maximizing throughput.
|
|
@@ -388,26 +620,7 @@ setInterval(() => {
|
|
|
388
620
|
});
|
|
389
621
|
```
|
|
390
622
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
Perfect for API calls or database operations that need throttling:
|
|
394
|
-
|
|
395
|
-
```typescript
|
|
396
|
-
const apiTask = new Task({
|
|
397
|
-
name: 'APICall',
|
|
398
|
-
taskFunction: async (endpoint: string) => {
|
|
399
|
-
return await fetch(endpoint);
|
|
400
|
-
},
|
|
401
|
-
buffered: true,
|
|
402
|
-
bufferMax: 3, // Maximum 3 concurrent executions
|
|
403
|
-
execDelay: 1000, // Wait 1 second between executions
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// These will be automatically throttled
|
|
407
|
-
for (let i = 0; i < 10; i++) {
|
|
408
|
-
apiTask.trigger(`/api/data/${i}`);
|
|
409
|
-
}
|
|
410
|
-
```
|
|
623
|
+
## Common Patterns ๐จ
|
|
411
624
|
|
|
412
625
|
### Task Chains - Sequential Workflows
|
|
413
626
|
|
|
@@ -488,8 +701,17 @@ import { Task, TaskManager } from '@push.rocks/taskbuffer';
|
|
|
488
701
|
|
|
489
702
|
const backupTask = new Task({
|
|
490
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,
|
|
491
708
|
taskFunction: async () => {
|
|
709
|
+
backupTask.notifyStep('dump');
|
|
492
710
|
await performBackup();
|
|
711
|
+
|
|
712
|
+
backupTask.notifyStep('upload');
|
|
713
|
+
await uploadToS3();
|
|
714
|
+
|
|
493
715
|
console.log(`Backup completed at ${new Date().toISOString()}`);
|
|
494
716
|
},
|
|
495
717
|
});
|
|
@@ -498,11 +720,14 @@ const manager = new TaskManager();
|
|
|
498
720
|
|
|
499
721
|
// Add and schedule tasks
|
|
500
722
|
manager.addAndScheduleTask(backupTask, '0 0 * * *'); // Daily at midnight
|
|
501
|
-
manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes
|
|
502
723
|
|
|
503
724
|
// Start the scheduler
|
|
504
725
|
manager.start();
|
|
505
726
|
|
|
727
|
+
// Monitor scheduled tasks
|
|
728
|
+
const scheduled = manager.getScheduledTasks();
|
|
729
|
+
console.log('Scheduled tasks:', scheduled);
|
|
730
|
+
|
|
506
731
|
// Later... stop if needed
|
|
507
732
|
manager.stop();
|
|
508
733
|
```
|
|
@@ -598,45 +823,6 @@ runner.registerTask(imageResizeTask);
|
|
|
598
823
|
runner.start();
|
|
599
824
|
```
|
|
600
825
|
|
|
601
|
-
### Buffer Management Strategies
|
|
602
|
-
|
|
603
|
-
Fine-tune concurrent execution behavior:
|
|
604
|
-
|
|
605
|
-
```typescript
|
|
606
|
-
const task = new Task({
|
|
607
|
-
name: 'ResourceIntensive',
|
|
608
|
-
taskFunction: async () => {
|
|
609
|
-
/* ... */
|
|
610
|
-
},
|
|
611
|
-
buffered: true,
|
|
612
|
-
bufferMax: 5, // Max 5 concurrent
|
|
613
|
-
execDelay: 100, // 100ms between starts
|
|
614
|
-
timeout: 30000, // 30 second timeout
|
|
615
|
-
});
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
### Cycle Detection and Prevention
|
|
619
|
-
|
|
620
|
-
TaskBuffer automatically detects and prevents circular dependencies:
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
const taskA = new Task({
|
|
624
|
-
name: 'TaskA',
|
|
625
|
-
taskFunction: async () => {
|
|
626
|
-
/* ... */
|
|
627
|
-
},
|
|
628
|
-
preTask: taskB, // This would create a cycle
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
const taskB = new Task({
|
|
632
|
-
name: 'TaskB',
|
|
633
|
-
taskFunction: async () => {
|
|
634
|
-
/* ... */
|
|
635
|
-
},
|
|
636
|
-
preTask: taskA, // Circular dependency detected!
|
|
637
|
-
});
|
|
638
|
-
```
|
|
639
|
-
|
|
640
826
|
### Dynamic Task Creation
|
|
641
827
|
|
|
642
828
|
Create tasks on-the-fly based on runtime conditions:
|
|
@@ -647,8 +833,17 @@ const dynamicWorkflow = async (config: Config) => {
|
|
|
647
833
|
(step) =>
|
|
648
834
|
new Task({
|
|
649
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,
|
|
650
841
|
taskFunction: async (input) => {
|
|
651
|
-
|
|
842
|
+
for (const substep of step.substeps || []) {
|
|
843
|
+
task.notifyStep(substep.id);
|
|
844
|
+
await processStep(substep, input);
|
|
845
|
+
}
|
|
846
|
+
return input;
|
|
652
847
|
},
|
|
653
848
|
}),
|
|
654
849
|
);
|
|
@@ -666,26 +861,46 @@ const dynamicWorkflow = async (config: Config) => {
|
|
|
666
861
|
|
|
667
862
|
### Task Options
|
|
668
863
|
|
|
669
|
-
| Option | Type | Description
|
|
670
|
-
| -------------- | ---------- |
|
|
671
|
-
| `name` | `string` | Unique identifier for the task
|
|
672
|
-
| `taskFunction` | `Function` | Async function to execute
|
|
673
|
-
| `
|
|
674
|
-
| `
|
|
675
|
-
| `
|
|
676
|
-
| `
|
|
677
|
-
| `
|
|
678
|
-
| `
|
|
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 |
|
|
679
886
|
|
|
680
887
|
### TaskManager Methods
|
|
681
888
|
|
|
682
|
-
| Method
|
|
683
|
-
|
|
|
684
|
-
| `addTask(task
|
|
685
|
-
| `
|
|
686
|
-
| `
|
|
687
|
-
| `
|
|
688
|
-
| `
|
|
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 |
|
|
689
904
|
|
|
690
905
|
### Taskchain Methods
|
|
691
906
|
|
|
@@ -703,14 +918,20 @@ const dynamicWorkflow = async (config: Config) => {
|
|
|
703
918
|
3. **Implement proper error handling**: Use try-catch in task functions
|
|
704
919
|
4. **Monitor task execution**: Use the built-in stats and logging
|
|
705
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
|
|
706
922
|
|
|
707
923
|
## Error Handling ๐ก๏ธ
|
|
708
924
|
|
|
709
925
|
```typescript
|
|
710
926
|
const robustTask = new Task({
|
|
711
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,
|
|
712
932
|
taskFunction: async (input) => {
|
|
713
933
|
try {
|
|
934
|
+
robustTask.notifyStep('try');
|
|
714
935
|
return await riskyOperation(input);
|
|
715
936
|
} catch (error) {
|
|
716
937
|
// Log error
|
|
@@ -718,6 +939,7 @@ const robustTask = new Task({
|
|
|
718
939
|
|
|
719
940
|
// Optionally retry
|
|
720
941
|
if (error.retryable) {
|
|
942
|
+
robustTask.notifyStep('retry');
|
|
721
943
|
return await riskyOperation(input);
|
|
722
944
|
}
|
|
723
945
|
|
|
@@ -731,12 +953,20 @@ const robustTask = new Task({
|
|
|
731
953
|
|
|
732
954
|
## Real-World Examples ๐
|
|
733
955
|
|
|
734
|
-
### API Rate Limiting
|
|
956
|
+
### API Rate Limiting with Progress
|
|
735
957
|
|
|
736
958
|
```typescript
|
|
737
959
|
const apiClient = new Task({
|
|
738
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,
|
|
739
965
|
taskFunction: async (endpoint: string) => {
|
|
966
|
+
apiClient.notifyStep('wait');
|
|
967
|
+
await delay(100); // Rate limiting
|
|
968
|
+
|
|
969
|
+
apiClient.notifyStep('call');
|
|
740
970
|
return await fetch(`https://api.example.com${endpoint}`);
|
|
741
971
|
},
|
|
742
972
|
buffered: true,
|
|
@@ -745,22 +975,43 @@ const apiClient = new Task({
|
|
|
745
975
|
});
|
|
746
976
|
```
|
|
747
977
|
|
|
748
|
-
### Database Migration Pipeline
|
|
978
|
+
### Database Migration Pipeline with Progress
|
|
749
979
|
|
|
750
980
|
```typescript
|
|
751
981
|
const migrationChain = new Taskchain({
|
|
752
982
|
name: 'DatabaseMigration',
|
|
753
983
|
taskArray: [
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
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
|
|
759
1007
|
],
|
|
760
1008
|
});
|
|
1009
|
+
|
|
1010
|
+
// Execute with progress monitoring
|
|
1011
|
+
const result = await migrationChain.trigger();
|
|
761
1012
|
```
|
|
762
1013
|
|
|
763
|
-
### Microservice Health Monitoring
|
|
1014
|
+
### Microservice Health Monitoring Dashboard
|
|
764
1015
|
|
|
765
1016
|
```typescript
|
|
766
1017
|
const healthMonitor = new TaskManager();
|
|
@@ -768,36 +1019,89 @@ const healthMonitor = new TaskManager();
|
|
|
768
1019
|
services.forEach((service) => {
|
|
769
1020
|
const healthCheck = new Task({
|
|
770
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,
|
|
771
1027
|
taskFunction: async () => {
|
|
1028
|
+
healthCheck.notifyStep('ping');
|
|
1029
|
+
const responsive = await ping(service.url);
|
|
1030
|
+
|
|
1031
|
+
healthCheck.notifyStep('check');
|
|
772
1032
|
const healthy = await checkHealth(service.url);
|
|
1033
|
+
|
|
1034
|
+
healthCheck.notifyStep('report');
|
|
773
1035
|
if (!healthy) {
|
|
774
1036
|
await alertOps(service);
|
|
775
1037
|
}
|
|
1038
|
+
|
|
1039
|
+
return { service: service.name, healthy, timestamp: Date.now() };
|
|
776
1040
|
},
|
|
777
1041
|
});
|
|
778
1042
|
|
|
779
1043
|
healthMonitor.addAndScheduleTask(healthCheck, '*/1 * * * *'); // Every minute
|
|
780
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
|
+
});
|
|
781
1061
|
```
|
|
782
1062
|
|
|
783
1063
|
## Testing ๐งช
|
|
784
1064
|
|
|
785
1065
|
```typescript
|
|
786
1066
|
import { expect, tap } from '@git.zone/tstest';
|
|
787
|
-
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
|
+
});
|
|
788
1088
|
|
|
789
|
-
tap.test('should
|
|
790
|
-
const
|
|
791
|
-
|
|
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);
|
|
792
1100
|
});
|
|
793
1101
|
|
|
794
1102
|
tap.start();
|
|
795
1103
|
```
|
|
796
1104
|
|
|
797
|
-
## Contributing ๐ค
|
|
798
|
-
|
|
799
|
-
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
800
|
-
|
|
801
1105
|
## Support ๐ฌ
|
|
802
1106
|
|
|
803
1107
|
- ๐ง Email: [hello@task.vc](mailto:hello@task.vc)
|
|
@@ -806,7 +1110,7 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
|
|
|
806
1110
|
|
|
807
1111
|
## License and Legal Information
|
|
808
1112
|
|
|
809
|
-
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.
|
|
810
1114
|
|
|
811
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.
|
|
812
1116
|
|
|
@@ -821,4 +1125,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|
|
821
1125
|
|
|
822
1126
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
|
823
1127
|
|
|
824
|
-
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.
|