@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/dist_ts/00_commitinfo_data.js +1 -1
- 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 +715 -95
- 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,27 +70,558 @@ const myTask = new Task({
|
|
|
72
70
|
const result = await myTask.trigger();
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
###
|
|
73
|
+
### Task with Progress Steps ๐
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
Track granular progress for complex operations - perfect for UI progress bars:
|
|
78
76
|
|
|
79
77
|
```typescript
|
|
80
|
-
const
|
|
81
|
-
name: '
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
87
|
-
execDelay: 1000, // Wait 1 second between executions
|
|
438
|
+
bufferMax: 3 // Maximum 3 concurrent executions
|
|
88
439
|
});
|
|
89
440
|
|
|
90
|
-
//
|
|
441
|
+
// Trigger 10 executions rapidly
|
|
91
442
|
for (let i = 0; i < 10; i++) {
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
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
|
|
367
|
-
|
|
|
368
|
-
| `addTask(task
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
372
|
-
| `
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
474
|
-
const
|
|
475
|
-
|
|
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.
|