@push.rocks/taskbuffer 4.0.0 → 4.1.1
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 +1 -1
- package/dist_ts/taskbuffer.classes.task.d.ts +9 -1
- package/dist_ts/taskbuffer.classes.task.js +36 -2
- package/dist_ts/taskbuffer.classes.taskmanager.d.ts +6 -1
- package/dist_ts/taskbuffer.classes.taskmanager.js +27 -3
- package/dist_ts/taskbuffer.interfaces.d.ts +9 -0
- package/dist_ts_web/ts/index.d.ts +1 -1
- package/dist_ts_web/ts/taskbuffer.classes.task.d.ts +9 -1
- package/dist_ts_web/ts/taskbuffer.classes.task.js +36 -2
- package/dist_ts_web/ts/taskbuffer.classes.taskmanager.d.ts +6 -1
- package/dist_ts_web/ts/taskbuffer.classes.taskmanager.js +27 -3
- package/dist_ts_web/ts/taskbuffer.interfaces.d.ts +9 -0
- package/dist_ts_web/ts_web/00_commitinfo_data.js +1 -1
- package/package.json +1 -1
- package/readme.hints.md +14 -0
- package/readme.md +538 -408
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -1
- package/ts/taskbuffer.classes.task.ts +43 -3
- package/ts/taskbuffer.classes.taskmanager.ts +30 -3
- package/ts/taskbuffer.interfaces.ts +11 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
package/readme.md
CHANGED
|
@@ -1,163 +1,169 @@
|
|
|
1
1
|
# @push.rocks/taskbuffer 🚀
|
|
2
2
|
|
|
3
|
-
> **Modern TypeScript task orchestration with smart buffering, scheduling, and real-time
|
|
3
|
+
> **Modern TypeScript task orchestration with smart buffering, scheduling, labels, and real-time event streaming**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@push.rocks/taskbuffer)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
+
## Issue Reporting and Security
|
|
10
|
+
|
|
11
|
+
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
12
|
+
|
|
9
13
|
## 🌟 Features
|
|
10
14
|
|
|
11
|
-
- **🎯 Type-Safe Task Management**
|
|
12
|
-
- **📊 Real-Time Progress Tracking**
|
|
13
|
-
- **⚡ Smart Buffering**
|
|
14
|
-
- **⏰ Cron Scheduling**
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
15
|
+
- **🎯 Type-Safe Task Management** — Full TypeScript support with generics and type inference
|
|
16
|
+
- **📊 Real-Time Progress Tracking** — Step-based progress with percentage weights
|
|
17
|
+
- **⚡ Smart Buffering** — Intelligent request debouncing and batching
|
|
18
|
+
- **⏰ Cron Scheduling** — Schedule tasks with cron expressions
|
|
19
|
+
- **🔗 Task Chains & Parallel Execution** — Sequential and parallel task orchestration
|
|
20
|
+
- **🏷️ Labels** — Attach arbitrary `Record<string, string>` metadata (userId, tenantId, etc.) for multi-tenant filtering
|
|
21
|
+
- **📡 Push-Based Events** — rxjs `Subject<ITaskEvent>` on every Task and TaskManager for real-time state change notifications
|
|
22
|
+
- **🛡️ Error Handling** — Configurable error propagation with `catchErrors`, error tracking, and clear error state
|
|
23
|
+
- **🎨 Web Component Dashboard** — Built-in Lit-based dashboard for real-time task visualization
|
|
24
|
+
- **🌐 Distributed Coordination** — Abstract coordinator for multi-instance task deduplication
|
|
20
25
|
|
|
21
26
|
## 📦 Installation
|
|
22
27
|
|
|
23
28
|
```bash
|
|
24
|
-
npm install @push.rocks/taskbuffer
|
|
25
|
-
# or
|
|
26
29
|
pnpm add @push.rocks/taskbuffer
|
|
27
30
|
# or
|
|
28
|
-
|
|
31
|
+
npm install @push.rocks/taskbuffer
|
|
29
32
|
```
|
|
30
33
|
|
|
31
34
|
## 🚀 Quick Start
|
|
32
35
|
|
|
33
|
-
### Basic Task
|
|
36
|
+
### Basic Task
|
|
34
37
|
|
|
35
38
|
```typescript
|
|
36
|
-
import { Task
|
|
39
|
+
import { Task } from '@push.rocks/taskbuffer';
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// Your async logic here
|
|
44
|
-
return `Processed: ${data}`;
|
|
45
|
-
}
|
|
41
|
+
const greetTask = new Task({
|
|
42
|
+
name: 'Greet',
|
|
43
|
+
taskFunction: async (name) => {
|
|
44
|
+
return `Hello, ${name}!`;
|
|
45
|
+
},
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.log(result); // "Processed: my-data"
|
|
48
|
+
const result = await greetTask.trigger('World');
|
|
49
|
+
console.log(result); // "Hello, World!"
|
|
51
50
|
```
|
|
52
51
|
|
|
53
|
-
###
|
|
52
|
+
### Task with Steps & Progress 📊
|
|
54
53
|
|
|
55
54
|
```typescript
|
|
56
|
-
const
|
|
57
|
-
name: '
|
|
55
|
+
const deployTask = new Task({
|
|
56
|
+
name: 'Deploy',
|
|
58
57
|
steps: [
|
|
59
|
-
{ name: 'build', description: 'Building
|
|
58
|
+
{ name: 'build', description: 'Building app', percentage: 30 },
|
|
60
59
|
{ name: 'test', description: 'Running tests', percentage: 20 },
|
|
61
60
|
{ name: 'deploy', description: 'Deploying to server', percentage: 40 },
|
|
62
|
-
{ name: 'verify', description: 'Verifying deployment', percentage: 10 }
|
|
63
|
-
] as const,
|
|
64
|
-
taskFunction: async
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.notifyStep('test');
|
|
61
|
+
{ name: 'verify', description: 'Verifying deployment', percentage: 10 },
|
|
62
|
+
] as const,
|
|
63
|
+
taskFunction: async () => {
|
|
64
|
+
deployTask.notifyStep('build');
|
|
65
|
+
await buildApp();
|
|
66
|
+
|
|
67
|
+
deployTask.notifyStep('test');
|
|
70
68
|
await runTests();
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
|
|
70
|
+
deployTask.notifyStep('deploy');
|
|
73
71
|
await deployToServer();
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
|
|
73
|
+
deployTask.notifyStep('verify');
|
|
76
74
|
await verifyDeployment();
|
|
77
|
-
|
|
75
|
+
|
|
78
76
|
return 'Deployment successful!';
|
|
79
|
-
}
|
|
77
|
+
},
|
|
80
78
|
});
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
console.log(
|
|
84
|
-
console.log(
|
|
80
|
+
await deployTask.trigger();
|
|
81
|
+
console.log(deployTask.getProgress()); // 100
|
|
82
|
+
console.log(deployTask.getStepsMetadata()); // Step details with status
|
|
85
83
|
```
|
|
86
84
|
|
|
85
|
+
> **Note:** `notifyStep()` is fully type-safe — TypeScript only accepts step names you declared in the `steps` array when you use `as const`.
|
|
86
|
+
|
|
87
87
|
## 🎯 Core Concepts
|
|
88
88
|
|
|
89
|
-
### Task Buffering
|
|
89
|
+
### Task Buffering — Intelligent Request Management
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
Prevent overwhelming your system with rapid-fire requests:
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
94
|
const apiTask = new Task({
|
|
95
95
|
name: 'APIRequest',
|
|
96
|
+
buffered: true,
|
|
97
|
+
bufferMax: 5, // Maximum 5 concurrent executions
|
|
98
|
+
execDelay: 100, // Minimum 100ms between executions
|
|
96
99
|
taskFunction: async (endpoint) => {
|
|
97
|
-
return await fetch(endpoint).then(r => r.json());
|
|
100
|
+
return await fetch(endpoint).then((r) => r.json());
|
|
98
101
|
},
|
|
99
|
-
buffered: true,
|
|
100
|
-
bufferMax: 5, // Maximum 5 concurrent executions
|
|
101
|
-
execDelay: 100 // Minimum 100ms between executions
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
// Rapid fire 100 calls
|
|
104
|
+
// Rapid fire 100 calls — only bufferMax execute concurrently
|
|
105
105
|
for (let i = 0; i < 100; i++) {
|
|
106
106
|
apiTask.trigger(`/api/data/${i}`);
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
**Buffer Behavior:**
|
|
111
|
+
|
|
111
112
|
- First `bufferMax` calls execute immediately
|
|
112
113
|
- Additional calls are queued
|
|
113
114
|
- When buffer is full, new calls overwrite the last queued item
|
|
114
115
|
- Perfect for real-time data streams where only recent data matters
|
|
115
116
|
|
|
116
|
-
### Task Chains
|
|
117
|
+
### Task Chains — Sequential Workflows 🔗
|
|
117
118
|
|
|
118
|
-
Build complex workflows with automatic data flow:
|
|
119
|
+
Build complex workflows with automatic data flow between tasks:
|
|
119
120
|
|
|
120
121
|
```typescript
|
|
121
|
-
import {
|
|
122
|
+
import { Taskchain } from '@push.rocks/taskbuffer';
|
|
122
123
|
|
|
123
124
|
const fetchTask = new Task({
|
|
124
|
-
name: '
|
|
125
|
+
name: 'Fetch',
|
|
125
126
|
taskFunction: async () => {
|
|
126
|
-
const
|
|
127
|
-
return
|
|
128
|
-
}
|
|
127
|
+
const res = await fetch('/api/data');
|
|
128
|
+
return res.json();
|
|
129
|
+
},
|
|
129
130
|
});
|
|
130
131
|
|
|
131
132
|
const transformTask = new Task({
|
|
132
|
-
name: '
|
|
133
|
+
name: 'Transform',
|
|
133
134
|
taskFunction: async (data) => {
|
|
134
|
-
return data.map(item => ({
|
|
135
|
-
|
|
136
|
-
transformed: true,
|
|
137
|
-
timestamp: Date.now()
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
135
|
+
return data.map((item) => ({ ...item, transformed: true }));
|
|
136
|
+
},
|
|
140
137
|
});
|
|
141
138
|
|
|
142
139
|
const saveTask = new Task({
|
|
143
|
-
name: '
|
|
140
|
+
name: 'Save',
|
|
144
141
|
taskFunction: async (transformedData) => {
|
|
145
142
|
await database.save(transformedData);
|
|
146
143
|
return transformedData.length;
|
|
147
|
-
}
|
|
144
|
+
},
|
|
148
145
|
});
|
|
149
146
|
|
|
150
|
-
|
|
151
|
-
const dataChain = new Taskchain({
|
|
147
|
+
const pipeline = new Taskchain({
|
|
152
148
|
name: 'DataPipeline',
|
|
153
|
-
|
|
149
|
+
taskArray: [fetchTask, transformTask, saveTask],
|
|
154
150
|
});
|
|
155
151
|
|
|
156
|
-
const savedCount = await
|
|
152
|
+
const savedCount = await pipeline.trigger();
|
|
157
153
|
console.log(`Saved ${savedCount} items`);
|
|
158
154
|
```
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
Taskchain also supports dynamic mutation:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
pipeline.addTask(newTask); // Append to chain
|
|
160
|
+
pipeline.removeTask(oldTask); // Remove by reference (returns boolean)
|
|
161
|
+
pipeline.shiftTask(); // Remove & return first task
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Error context is rich — a chain failure includes the chain name, failing task name, task index, and preserves the original error as `.cause`.
|
|
165
|
+
|
|
166
|
+
### Parallel Execution — Concurrent Processing ⚡
|
|
161
167
|
|
|
162
168
|
Execute multiple tasks simultaneously:
|
|
163
169
|
|
|
@@ -165,21 +171,13 @@ Execute multiple tasks simultaneously:
|
|
|
165
171
|
import { Taskparallel } from '@push.rocks/taskbuffer';
|
|
166
172
|
|
|
167
173
|
const parallel = new Taskparallel({
|
|
168
|
-
|
|
169
|
-
tasks: [
|
|
170
|
-
emailTask,
|
|
171
|
-
smsTask,
|
|
172
|
-
pushNotificationTask,
|
|
173
|
-
webhookTask
|
|
174
|
-
]
|
|
174
|
+
taskArray: [emailTask, smsTask, pushNotificationTask, webhookTask],
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
const results = await parallel.trigger(notificationData);
|
|
179
|
-
// results = [emailResult, smsResult, pushResult, webhookResult]
|
|
177
|
+
await parallel.trigger(notificationData);
|
|
180
178
|
```
|
|
181
179
|
|
|
182
|
-
### Debounced Tasks
|
|
180
|
+
### Debounced Tasks — Smart Trigger Coalescing 🕐
|
|
183
181
|
|
|
184
182
|
Coalesce rapid triggers into a single execution after a quiet period:
|
|
185
183
|
|
|
@@ -187,441 +185,573 @@ Coalesce rapid triggers into a single execution after a quiet period:
|
|
|
187
185
|
import { TaskDebounced } from '@push.rocks/taskbuffer';
|
|
188
186
|
|
|
189
187
|
const searchTask = new TaskDebounced({
|
|
190
|
-
name: '
|
|
191
|
-
debounceTimeInMillis: 300,
|
|
188
|
+
name: 'Search',
|
|
189
|
+
debounceTimeInMillis: 300,
|
|
192
190
|
taskFunction: async (query) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
191
|
+
return await searchAPI(query);
|
|
192
|
+
},
|
|
196
193
|
});
|
|
197
194
|
|
|
198
|
-
// Rapid
|
|
195
|
+
// Rapid calls — only the last triggers after 300ms of quiet
|
|
199
196
|
searchTask.trigger('h');
|
|
200
197
|
searchTask.trigger('he');
|
|
201
198
|
searchTask.trigger('hel');
|
|
202
|
-
searchTask.trigger('hello'); //
|
|
199
|
+
searchTask.trigger('hello'); // ← this one fires
|
|
203
200
|
```
|
|
204
201
|
|
|
205
|
-
###
|
|
202
|
+
### TaskOnce — Single-Execution Guard
|
|
206
203
|
|
|
207
|
-
|
|
204
|
+
Ensure a task only runs once, regardless of how many times it's triggered:
|
|
208
205
|
|
|
209
206
|
```typescript
|
|
210
|
-
|
|
207
|
+
import { TaskOnce } from '@push.rocks/taskbuffer';
|
|
211
208
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
const initTask = new TaskOnce({
|
|
210
|
+
name: 'Init',
|
|
211
|
+
taskFunction: async () => {
|
|
212
|
+
await setupDatabase();
|
|
213
|
+
console.log('Initialized!');
|
|
214
|
+
},
|
|
215
|
+
});
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
await initTask.trigger(); // Runs
|
|
218
|
+
await initTask.trigger(); // No-op
|
|
219
|
+
await initTask.trigger(); // No-op
|
|
220
|
+
console.log(initTask.hasTriggered); // true
|
|
221
|
+
```
|
|
219
222
|
|
|
220
|
-
|
|
221
|
-
const metadata = taskManager.getTaskMetadata('DeployApplication');
|
|
222
|
-
console.log(metadata);
|
|
223
|
-
// {
|
|
224
|
-
// name: 'DeployApplication',
|
|
225
|
-
// status: 'idle' | 'running' | 'completed' | 'failed',
|
|
226
|
-
// steps: [...],
|
|
227
|
-
// currentProgress: 75,
|
|
228
|
-
// runCount: 12,
|
|
229
|
-
// lastRun: Date,
|
|
230
|
-
// buffered: false,
|
|
231
|
-
// bufferMax: undefined,
|
|
232
|
-
// version: '1.0.0',
|
|
233
|
-
// timeout: 30000
|
|
234
|
-
// }
|
|
223
|
+
### TaskRunner — Managed Queue with Concurrency Control
|
|
235
224
|
|
|
236
|
-
|
|
237
|
-
const scheduled = taskManager.getScheduledTasks();
|
|
238
|
-
scheduled.forEach(task => {
|
|
239
|
-
console.log(`${task.name}: Next run at ${task.nextRun}`);
|
|
240
|
-
});
|
|
225
|
+
Process a queue of tasks with a configurable parallelism limit:
|
|
241
226
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
trackProgress: true
|
|
245
|
-
});
|
|
246
|
-
console.log(report);
|
|
247
|
-
// {
|
|
248
|
-
// taskName: 'TempTask',
|
|
249
|
-
// startTime: Date,
|
|
250
|
-
// endTime: Date,
|
|
251
|
-
// duration: 1523,
|
|
252
|
-
// steps: [...],
|
|
253
|
-
// stepsCompleted: ['step1', 'step2'],
|
|
254
|
-
// progress: 100,
|
|
255
|
-
// result: any,
|
|
256
|
-
// error?: Error
|
|
257
|
-
// }
|
|
258
|
-
```
|
|
227
|
+
```typescript
|
|
228
|
+
import { TaskRunner } from '@push.rocks/taskbuffer';
|
|
259
229
|
|
|
260
|
-
|
|
230
|
+
const runner = new TaskRunner();
|
|
231
|
+
runner.setMaxParallelJobs(3); // Run up to 3 tasks concurrently
|
|
261
232
|
|
|
262
|
-
|
|
233
|
+
await runner.start();
|
|
263
234
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const taskManager = new TaskManager();
|
|
273
|
-
|
|
274
|
-
// Attach to dashboard
|
|
275
|
-
const dashboard = document.querySelector('taskbuffer-dashboard');
|
|
276
|
-
dashboard.taskManager = taskManager;
|
|
277
|
-
dashboard.refreshInterval = 500; // Update every 500ms
|
|
278
|
-
</script>
|
|
279
|
-
</head>
|
|
280
|
-
<body>
|
|
281
|
-
<taskbuffer-dashboard></taskbuffer-dashboard>
|
|
282
|
-
</body>
|
|
283
|
-
</html>
|
|
235
|
+
runner.addTask(taskA);
|
|
236
|
+
runner.addTask(taskB);
|
|
237
|
+
runner.addTask(taskC);
|
|
238
|
+
runner.addTask(taskD); // Queued until a slot opens
|
|
239
|
+
|
|
240
|
+
// When done:
|
|
241
|
+
await runner.stop();
|
|
284
242
|
```
|
|
285
243
|
|
|
286
|
-
|
|
287
|
-
- 📊 Real-time progress bars with step indicators
|
|
288
|
-
- 📈 Task execution history
|
|
289
|
-
- ⏰ Scheduled task information
|
|
290
|
-
- 🎯 Interactive task controls
|
|
291
|
-
- 🌓 Light/dark theme support
|
|
244
|
+
## 🏷️ Labels — Multi-Tenant Task Filtering
|
|
292
245
|
|
|
293
|
-
|
|
246
|
+
Attach arbitrary key-value labels to any task for filtering, grouping, or multi-tenant isolation:
|
|
294
247
|
|
|
295
|
-
|
|
248
|
+
```typescript
|
|
249
|
+
const task = new Task({
|
|
250
|
+
name: 'ProcessOrder',
|
|
251
|
+
labels: { userId: 'u-42', tenantId: 'acme-corp', priority: 'high' },
|
|
252
|
+
taskFunction: async (order) => {
|
|
253
|
+
/* ... */
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Manipulate labels at runtime
|
|
258
|
+
task.setLabel('region', 'eu-west');
|
|
259
|
+
task.getLabel('userId'); // 'u-42'
|
|
260
|
+
task.hasLabel('tenantId', 'acme-corp'); // true
|
|
261
|
+
task.removeLabel('priority'); // true
|
|
262
|
+
|
|
263
|
+
// Labels are included in metadata snapshots
|
|
264
|
+
const meta = task.getMetadata();
|
|
265
|
+
console.log(meta.labels); // { userId: 'u-42', tenantId: 'acme-corp', region: 'eu-west' }
|
|
266
|
+
```
|
|
296
267
|
|
|
297
|
-
|
|
268
|
+
### Filtering Tasks by Label in TaskManager
|
|
298
269
|
|
|
299
270
|
```typescript
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
});
|
|
271
|
+
const manager = new TaskManager();
|
|
272
|
+
manager.addTask(orderTask1); // labels: { tenantId: 'acme' }
|
|
273
|
+
manager.addTask(orderTask2); // labels: { tenantId: 'globex' }
|
|
274
|
+
manager.addTask(orderTask3); // labels: { tenantId: 'acme' }
|
|
275
|
+
|
|
276
|
+
const acmeTasks = manager.getTasksByLabel('tenantId', 'acme');
|
|
277
|
+
// → [orderTask1, orderTask3]
|
|
278
|
+
|
|
279
|
+
const acmeMetadata = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
280
|
+
// → [ITaskMetadata, ITaskMetadata]
|
|
312
281
|
```
|
|
313
282
|
|
|
314
|
-
|
|
283
|
+
## 📡 Push-Based Events — Real-Time Task Lifecycle
|
|
315
284
|
|
|
316
|
-
|
|
285
|
+
Every `Task` exposes an rxjs `Subject<ITaskEvent>` that emits events as the task progresses through its lifecycle:
|
|
317
286
|
|
|
318
287
|
```typescript
|
|
319
|
-
|
|
320
|
-
private tasks: Task[] = [];
|
|
321
|
-
private currentIndex = 0;
|
|
322
|
-
|
|
323
|
-
constructor(poolSize: number, taskConfig: any) {
|
|
324
|
-
for (let i = 0; i < poolSize; i++) {
|
|
325
|
-
this.tasks.push(new Task({
|
|
326
|
-
...taskConfig,
|
|
327
|
-
name: `${taskConfig.name}_${i}`
|
|
328
|
-
}));
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async execute(data: any) {
|
|
333
|
-
const task = this.tasks[this.currentIndex];
|
|
334
|
-
this.currentIndex = (this.currentIndex + 1) % this.tasks.length;
|
|
335
|
-
return await task.trigger(data);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
288
|
+
import type { ITaskEvent } from '@push.rocks/taskbuffer';
|
|
338
289
|
|
|
339
|
-
const
|
|
340
|
-
name: '
|
|
341
|
-
|
|
290
|
+
const task = new Task({
|
|
291
|
+
name: 'DataSync',
|
|
292
|
+
steps: [
|
|
293
|
+
{ name: 'fetch', description: 'Fetching data', percentage: 50 },
|
|
294
|
+
{ name: 'save', description: 'Saving data', percentage: 50 },
|
|
295
|
+
] as const,
|
|
296
|
+
taskFunction: async () => {
|
|
297
|
+
task.notifyStep('fetch');
|
|
298
|
+
const data = await fetchData();
|
|
299
|
+
task.notifyStep('save');
|
|
300
|
+
await saveData(data);
|
|
301
|
+
},
|
|
342
302
|
});
|
|
303
|
+
|
|
304
|
+
// Subscribe to individual task events
|
|
305
|
+
task.eventSubject.subscribe((event: ITaskEvent) => {
|
|
306
|
+
console.log(`[${event.type}] ${event.task.name} @ ${new Date(event.timestamp).toISOString()}`);
|
|
307
|
+
if (event.type === 'step') console.log(` Step: ${event.stepName}`);
|
|
308
|
+
if (event.type === 'failed') console.log(` Error: ${event.error}`);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await task.trigger();
|
|
312
|
+
// [started] DataSync @ 2025-01-26T...
|
|
313
|
+
// [step] DataSync @ 2025-01-26T...
|
|
314
|
+
// Step: fetch
|
|
315
|
+
// [step] DataSync @ 2025-01-26T...
|
|
316
|
+
// Step: save
|
|
317
|
+
// [completed] DataSync @ 2025-01-26T...
|
|
343
318
|
```
|
|
344
319
|
|
|
345
|
-
###
|
|
320
|
+
### Event Types
|
|
346
321
|
|
|
347
|
-
|
|
322
|
+
| Type | When | Extra Fields |
|
|
323
|
+
| --- | --- | --- |
|
|
324
|
+
| `'started'` | Task begins execution | — |
|
|
325
|
+
| `'step'` | `notifyStep()` is called | `stepName` |
|
|
326
|
+
| `'completed'` | Task finishes successfully | — |
|
|
327
|
+
| `'failed'` | Task throws an error | `error` (message string) |
|
|
328
|
+
|
|
329
|
+
Every event includes a full `ITaskMetadata` snapshot (including labels) at the time of emission.
|
|
330
|
+
|
|
331
|
+
### Aggregated Events on TaskManager
|
|
332
|
+
|
|
333
|
+
`TaskManager` automatically aggregates events from all added tasks into a single `taskSubject`:
|
|
348
334
|
|
|
349
335
|
```typescript
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, retryCount)));
|
|
359
|
-
return await resilientTask.trigger(data, retryCount + 1);
|
|
360
|
-
}
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
336
|
+
const manager = new TaskManager();
|
|
337
|
+
manager.addTask(syncTask);
|
|
338
|
+
manager.addTask(reportTask);
|
|
339
|
+
manager.addTask(cleanupTask);
|
|
340
|
+
|
|
341
|
+
// Single subscription for ALL task events
|
|
342
|
+
manager.taskSubject.subscribe((event) => {
|
|
343
|
+
sendToMonitoringDashboard(event);
|
|
364
344
|
});
|
|
345
|
+
|
|
346
|
+
// Events stop flowing for a task after removal
|
|
347
|
+
manager.removeTask(syncTask);
|
|
365
348
|
```
|
|
366
349
|
|
|
367
|
-
|
|
350
|
+
`manager.stop()` automatically cleans up all event subscriptions.
|
|
351
|
+
|
|
352
|
+
## 🛡️ Error Handling
|
|
368
353
|
|
|
369
|
-
|
|
354
|
+
By default, `trigger()` **rejects** when the task function throws — errors propagate naturally:
|
|
370
355
|
|
|
371
356
|
```typescript
|
|
372
|
-
const
|
|
373
|
-
name: '
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
{ name: 'notify', description: 'Sending notifications', percentage: 20 }
|
|
378
|
-
] as const,
|
|
379
|
-
taskFunction: async function(data) {
|
|
380
|
-
this.notifyStep('validate');
|
|
381
|
-
const validated = await validationTask.trigger(data);
|
|
382
|
-
|
|
383
|
-
this.notifyStep('process');
|
|
384
|
-
const processed = await processingTask.trigger(validated);
|
|
385
|
-
|
|
386
|
-
this.notifyStep('notify');
|
|
387
|
-
await notificationTask.trigger(processed);
|
|
388
|
-
|
|
389
|
-
return processed;
|
|
390
|
-
}
|
|
357
|
+
const task = new Task({
|
|
358
|
+
name: 'RiskyOp',
|
|
359
|
+
taskFunction: async () => {
|
|
360
|
+
throw new Error('something broke');
|
|
361
|
+
},
|
|
391
362
|
});
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
await task.trigger();
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error(err.message); // "something broke"
|
|
368
|
+
}
|
|
392
369
|
```
|
|
393
370
|
|
|
394
|
-
|
|
371
|
+
### Swallowing Errors with `catchErrors`
|
|
395
372
|
|
|
396
|
-
|
|
373
|
+
Set `catchErrors: true` to swallow errors and return `undefined` instead of rejecting:
|
|
397
374
|
|
|
398
375
|
```typescript
|
|
399
|
-
|
|
400
|
-
name
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
taskSetup?: Function; // One-time setup function
|
|
409
|
-
beforeTask?: Function; // Runs before each execution
|
|
410
|
-
afterTask?: Function; // Runs after each execution
|
|
411
|
-
}
|
|
376
|
+
const task = new Task({
|
|
377
|
+
name: 'BestEffort',
|
|
378
|
+
catchErrors: true,
|
|
379
|
+
taskFunction: async () => {
|
|
380
|
+
throw new Error('non-critical');
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const result = await task.trigger(); // undefined (no throw)
|
|
412
385
|
```
|
|
413
386
|
|
|
414
|
-
###
|
|
387
|
+
### Error State Tracking
|
|
388
|
+
|
|
389
|
+
Regardless of `catchErrors`, the task tracks errors:
|
|
415
390
|
|
|
416
391
|
```typescript
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
392
|
+
console.log(task.lastError); // Error object (or undefined)
|
|
393
|
+
console.log(task.errorCount); // Number of failures across all runs
|
|
394
|
+
console.log(task.getMetadata().status); // 'failed'
|
|
395
|
+
|
|
396
|
+
task.clearError(); // Resets lastError to undefined (errorCount stays)
|
|
422
397
|
```
|
|
423
398
|
|
|
424
|
-
|
|
399
|
+
On a subsequent successful run, `lastError` is automatically cleared.
|
|
425
400
|
|
|
426
|
-
|
|
401
|
+
## 📋 TaskManager — Centralized Orchestration
|
|
427
402
|
|
|
428
403
|
```typescript
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
404
|
+
const manager = new TaskManager();
|
|
405
|
+
|
|
406
|
+
// Add tasks
|
|
407
|
+
manager.addTask(dataProcessor);
|
|
408
|
+
manager.addTask(deployTask);
|
|
409
|
+
|
|
410
|
+
// Schedule with cron expressions
|
|
411
|
+
manager.addAndScheduleTask(backupTask, '0 2 * * *'); // Daily at 2 AM
|
|
412
|
+
manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes
|
|
413
|
+
|
|
414
|
+
// Query metadata
|
|
415
|
+
const meta = manager.getTaskMetadata('Deploy');
|
|
416
|
+
console.log(meta);
|
|
417
|
+
// {
|
|
418
|
+
// name: 'Deploy',
|
|
419
|
+
// status: 'completed',
|
|
420
|
+
// steps: [...],
|
|
421
|
+
// currentProgress: 100,
|
|
422
|
+
// runCount: 3,
|
|
423
|
+
// labels: { env: 'production' },
|
|
424
|
+
// lastError: undefined,
|
|
425
|
+
// errorCount: 0,
|
|
426
|
+
// ...
|
|
427
|
+
// }
|
|
428
|
+
|
|
429
|
+
// All tasks at once
|
|
430
|
+
const allMeta = manager.getAllTasksMetadata();
|
|
431
|
+
|
|
432
|
+
// Scheduled task info
|
|
433
|
+
const scheduled = manager.getScheduledTasks();
|
|
434
|
+
const nextRuns = manager.getNextScheduledRuns(5);
|
|
435
|
+
|
|
436
|
+
// Trigger by name
|
|
437
|
+
await manager.triggerTaskByName('Deploy');
|
|
438
|
+
|
|
439
|
+
// One-shot: add, execute, collect report, remove
|
|
440
|
+
const report = await manager.addExecuteRemoveTask(temporaryTask);
|
|
441
|
+
console.log(report);
|
|
442
|
+
// {
|
|
443
|
+
// taskName: 'TempTask',
|
|
444
|
+
// startTime: 1706284800000,
|
|
445
|
+
// endTime: 1706284801523,
|
|
446
|
+
// duration: 1523,
|
|
447
|
+
// steps: [...],
|
|
448
|
+
// stepsCompleted: ['step1', 'step2'],
|
|
449
|
+
// progress: 100,
|
|
450
|
+
// result: any
|
|
451
|
+
// }
|
|
452
|
+
|
|
453
|
+
// Lifecycle
|
|
454
|
+
await manager.start(); // Starts cron scheduling + distributed coordinator
|
|
455
|
+
await manager.stop(); // Stops scheduling, cleans up event subscriptions
|
|
433
456
|
```
|
|
434
457
|
|
|
435
|
-
###
|
|
458
|
+
### Remove Tasks
|
|
436
459
|
|
|
437
460
|
```typescript
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
461
|
+
manager.removeTask(task); // Removes from map and unsubscribes event forwarding
|
|
462
|
+
manager.descheduleTaskByName('Deploy'); // Remove cron schedule only
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## 🎨 Web Component Dashboard
|
|
466
|
+
|
|
467
|
+
Visualize your tasks in real-time with the included Lit-based web component:
|
|
468
|
+
|
|
469
|
+
```html
|
|
470
|
+
<script type="module">
|
|
471
|
+
import { TaskManager } from '@push.rocks/taskbuffer';
|
|
472
|
+
import '@push.rocks/taskbuffer/dist_ts_web/taskbuffer-dashboard.js';
|
|
473
|
+
|
|
474
|
+
const manager = new TaskManager();
|
|
475
|
+
// ... add and schedule tasks ...
|
|
476
|
+
|
|
477
|
+
const dashboard = document.querySelector('taskbuffer-dashboard');
|
|
478
|
+
dashboard.taskManager = manager;
|
|
479
|
+
dashboard.refreshInterval = 500; // Poll every 500ms
|
|
480
|
+
</script>
|
|
481
|
+
|
|
482
|
+
<taskbuffer-dashboard></taskbuffer-dashboard>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
The dashboard provides:
|
|
486
|
+
|
|
487
|
+
- 📊 Real-time progress bars with step indicators
|
|
488
|
+
- 📈 Task execution history and metadata
|
|
489
|
+
- ⏰ Scheduled task information with next-run times
|
|
490
|
+
- 🌓 Light/dark theme support
|
|
491
|
+
|
|
492
|
+
## 🌐 Distributed Coordination
|
|
493
|
+
|
|
494
|
+
For multi-instance deployments, extend `AbstractDistributedCoordinator` to prevent duplicate task execution:
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
import { TaskManager, distributedCoordination } from '@push.rocks/taskbuffer';
|
|
498
|
+
|
|
499
|
+
class RedisCoordinator extends distributedCoordination.AbstractDistributedCoordinator {
|
|
500
|
+
async fireDistributedTaskRequest(request) {
|
|
501
|
+
// Implement leader election / distributed lock via Redis
|
|
502
|
+
return { shouldTrigger: true, considered: true, rank: 1, reason: 'elected', ...request };
|
|
503
|
+
}
|
|
504
|
+
async updateDistributedTaskRequest(request) {
|
|
505
|
+
/* update status */
|
|
506
|
+
}
|
|
507
|
+
async start() {
|
|
508
|
+
/* connect */
|
|
509
|
+
}
|
|
510
|
+
async stop() {
|
|
511
|
+
/* disconnect */
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const manager = new TaskManager({
|
|
516
|
+
distributedCoordinator: new RedisCoordinator(),
|
|
444
517
|
});
|
|
445
518
|
```
|
|
446
519
|
|
|
447
|
-
|
|
520
|
+
When a distributed coordinator is configured, scheduled tasks consult it before executing — only the elected instance runs the task.
|
|
521
|
+
|
|
522
|
+
## 🧩 Advanced Patterns
|
|
523
|
+
|
|
524
|
+
### Pre-Task & After-Task Hooks
|
|
525
|
+
|
|
526
|
+
Run setup/teardown tasks automatically:
|
|
448
527
|
|
|
449
528
|
```typescript
|
|
450
|
-
|
|
451
|
-
|
|
529
|
+
const mainTask = new Task({
|
|
530
|
+
name: 'MainWork',
|
|
531
|
+
preTask: new Task({
|
|
532
|
+
name: 'Setup',
|
|
533
|
+
taskFunction: async () => {
|
|
534
|
+
console.log('Setting up...');
|
|
535
|
+
},
|
|
536
|
+
}),
|
|
537
|
+
afterTask: new Task({
|
|
538
|
+
name: 'Cleanup',
|
|
539
|
+
taskFunction: async () => {
|
|
540
|
+
console.log('Cleaning up...');
|
|
541
|
+
},
|
|
542
|
+
}),
|
|
543
|
+
taskFunction: async () => {
|
|
544
|
+
console.log('Doing work...');
|
|
545
|
+
return 'done';
|
|
546
|
+
},
|
|
547
|
+
});
|
|
452
548
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
549
|
+
await mainTask.trigger();
|
|
550
|
+
// Setting up... → Doing work... → Cleaning up...
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### One-Time Setup Functions
|
|
554
|
+
|
|
555
|
+
Run an expensive initialization exactly once, before the first execution:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
const task = new Task({
|
|
559
|
+
name: 'DBQuery',
|
|
560
|
+
taskSetup: async () => {
|
|
561
|
+
const pool = await createConnectionPool();
|
|
562
|
+
return pool; // This becomes `setupValue`
|
|
563
|
+
},
|
|
564
|
+
taskFunction: async (input, pool) => {
|
|
565
|
+
return await pool.query(input);
|
|
566
|
+
},
|
|
471
567
|
});
|
|
568
|
+
|
|
569
|
+
await task.trigger('SELECT * FROM users'); // Setup runs here
|
|
570
|
+
await task.trigger('SELECT * FROM orders'); // Setup skipped, pool reused
|
|
472
571
|
```
|
|
473
572
|
|
|
474
|
-
|
|
573
|
+
### Blocking Tasks
|
|
475
574
|
|
|
476
|
-
|
|
575
|
+
Make one task wait for another to finish before executing:
|
|
477
576
|
|
|
478
577
|
```typescript
|
|
479
|
-
const
|
|
480
|
-
name: '
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
taskFunction: async (endpoint, data) => {
|
|
485
|
-
return await fetch(endpoint, {
|
|
486
|
-
method: 'POST',
|
|
487
|
-
body: JSON.stringify(data)
|
|
488
|
-
});
|
|
489
|
-
}
|
|
578
|
+
const initTask = new Task({
|
|
579
|
+
name: 'Init',
|
|
580
|
+
taskFunction: async () => {
|
|
581
|
+
await initializeSystem();
|
|
582
|
+
},
|
|
490
583
|
});
|
|
584
|
+
|
|
585
|
+
const workerTask = new Task({
|
|
586
|
+
name: 'Worker',
|
|
587
|
+
taskFunction: async () => {
|
|
588
|
+
await doWork();
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
workerTask.blockingTasks.push(initTask);
|
|
593
|
+
|
|
594
|
+
// Triggering worker will automatically wait for init to complete
|
|
595
|
+
initTask.trigger();
|
|
596
|
+
workerTask.trigger(); // Waits until initTask.finished resolves
|
|
491
597
|
```
|
|
492
598
|
|
|
493
599
|
### Database Migration Pipeline
|
|
494
600
|
|
|
495
601
|
```typescript
|
|
496
|
-
const
|
|
602
|
+
const migration = new Taskchain({
|
|
497
603
|
name: 'DatabaseMigration',
|
|
498
|
-
|
|
499
|
-
backupDatabaseTask,
|
|
500
|
-
validateSchemaTask,
|
|
501
|
-
runMigrationsTask,
|
|
502
|
-
verifyIntegrityTask,
|
|
503
|
-
updateIndexesTask
|
|
504
|
-
]
|
|
604
|
+
taskArray: [backupTask, validateSchemaTask, runMigrationsTask, verifyIntegrityTask],
|
|
505
605
|
});
|
|
506
606
|
|
|
507
|
-
// Execute with rollback on failure
|
|
508
607
|
try {
|
|
509
|
-
await
|
|
608
|
+
await migration.trigger();
|
|
510
609
|
console.log('Migration successful!');
|
|
511
610
|
} catch (error) {
|
|
611
|
+
// error includes chain name, failing task name, index, and original cause
|
|
612
|
+
console.error(error.message);
|
|
512
613
|
await rollbackTask.trigger();
|
|
513
|
-
throw error;
|
|
514
614
|
}
|
|
515
615
|
```
|
|
516
616
|
|
|
517
|
-
###
|
|
617
|
+
### Multi-Tenant SaaS Monitoring
|
|
618
|
+
|
|
619
|
+
Combine labels + events for a real-time multi-tenant dashboard:
|
|
518
620
|
|
|
519
621
|
```typescript
|
|
520
|
-
const
|
|
622
|
+
const manager = new TaskManager();
|
|
521
623
|
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
] as const,
|
|
533
|
-
taskFunction: async function(job) {
|
|
534
|
-
this.notifyStep('download');
|
|
535
|
-
const image = await downloadImage(job.url);
|
|
536
|
-
|
|
537
|
-
this.notifyStep('resize');
|
|
538
|
-
const resized = await resizeImage(image, job.dimensions);
|
|
539
|
-
|
|
540
|
-
this.notifyStep('optimize');
|
|
541
|
-
const optimized = await optimizeImage(resized);
|
|
542
|
-
|
|
543
|
-
this.notifyStep('upload');
|
|
544
|
-
return await uploadToCDN(optimized);
|
|
545
|
-
}
|
|
546
|
-
});
|
|
624
|
+
// Create tenant-scoped tasks
|
|
625
|
+
function createTenantTask(tenantId: string, taskName: string, fn: () => Promise<any>) {
|
|
626
|
+
const task = new Task({
|
|
627
|
+
name: `${tenantId}:${taskName}`,
|
|
628
|
+
labels: { tenantId },
|
|
629
|
+
taskFunction: fn,
|
|
630
|
+
});
|
|
631
|
+
manager.addTask(task);
|
|
632
|
+
return task;
|
|
633
|
+
}
|
|
547
634
|
|
|
548
|
-
|
|
635
|
+
createTenantTask('acme', 'sync', async () => syncData('acme'));
|
|
636
|
+
createTenantTask('globex', 'sync', async () => syncData('globex'));
|
|
549
637
|
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
638
|
+
// Stream events to tenant-specific WebSocket channels
|
|
639
|
+
manager.taskSubject.subscribe((event) => {
|
|
640
|
+
const tenantId = event.task.labels?.tenantId;
|
|
641
|
+
if (tenantId) {
|
|
642
|
+
wss.broadcast(tenantId, JSON.stringify(event));
|
|
643
|
+
}
|
|
554
644
|
});
|
|
555
|
-
```
|
|
556
645
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
2. **Set Appropriate Delays** - Use `execDelay` to prevent API rate limits
|
|
561
|
-
3. **Leverage Task Pools** - Distribute load across multiple task instances
|
|
562
|
-
4. **Monitor Progress** - Use step tracking for long-running operations
|
|
563
|
-
5. **Clean Up** - Use `addExecuteRemoveTask` for one-time operations
|
|
646
|
+
// Query tasks for a specific tenant
|
|
647
|
+
const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
648
|
+
```
|
|
564
649
|
|
|
565
|
-
##
|
|
650
|
+
## 📚 API Reference
|
|
566
651
|
|
|
567
|
-
|
|
652
|
+
### Classes
|
|
653
|
+
|
|
654
|
+
| Class | Description |
|
|
655
|
+
| --- | --- |
|
|
656
|
+
| `Task<T, TSteps>` | Core task unit with optional step tracking, labels, and event streaming |
|
|
657
|
+
| `TaskManager` | Centralized orchestrator with scheduling, label queries, and aggregated events |
|
|
658
|
+
| `Taskchain` | Sequential task executor with data flow between tasks |
|
|
659
|
+
| `Taskparallel` | Concurrent task executor via `Promise.all()` |
|
|
660
|
+
| `TaskOnce` | Single-execution guard |
|
|
661
|
+
| `TaskDebounced` | Debounced task using rxjs |
|
|
662
|
+
| `TaskRunner` | Sequential queue with configurable parallelism |
|
|
663
|
+
| `TaskStep` | Step tracking unit (internal, exposed via metadata) |
|
|
664
|
+
|
|
665
|
+
### Task Methods
|
|
666
|
+
|
|
667
|
+
| Method | Returns | Description |
|
|
668
|
+
| --- | --- | --- |
|
|
669
|
+
| `trigger(input?)` | `Promise<any>` | Execute the task |
|
|
670
|
+
| `notifyStep(name)` | `void` | Advance to named step (type-safe) |
|
|
671
|
+
| `getProgress()` | `number` | Current progress 0–100 |
|
|
672
|
+
| `getStepsMetadata()` | `ITaskStep[]` | Step details with status |
|
|
673
|
+
| `getMetadata()` | `ITaskMetadata` | Full task metadata snapshot |
|
|
674
|
+
| `setLabel(key, value)` | `void` | Set a label |
|
|
675
|
+
| `getLabel(key)` | `string \| undefined` | Get a label value |
|
|
676
|
+
| `removeLabel(key)` | `boolean` | Remove a label |
|
|
677
|
+
| `hasLabel(key, value?)` | `boolean` | Check label existence / value |
|
|
678
|
+
| `clearError()` | `void` | Reset `lastError` to undefined |
|
|
679
|
+
|
|
680
|
+
### Task Properties
|
|
681
|
+
|
|
682
|
+
| Property | Type | Description |
|
|
683
|
+
| --- | --- | --- |
|
|
684
|
+
| `name` | `string` | Task identifier |
|
|
685
|
+
| `running` | `boolean` | Whether the task is currently executing |
|
|
686
|
+
| `idle` | `boolean` | Inverse of `running` |
|
|
687
|
+
| `labels` | `Record<string, string>` | Attached labels |
|
|
688
|
+
| `eventSubject` | `Subject<ITaskEvent>` | rxjs Subject emitting lifecycle events |
|
|
689
|
+
| `lastError` | `Error \| undefined` | Last error encountered |
|
|
690
|
+
| `errorCount` | `number` | Total error count across all runs |
|
|
691
|
+
| `runCount` | `number` | Total execution count |
|
|
692
|
+
| `lastRun` | `Date \| undefined` | Timestamp of last execution |
|
|
693
|
+
| `blockingTasks` | `Task[]` | Tasks that must finish before this one starts |
|
|
694
|
+
|
|
695
|
+
### TaskManager Methods
|
|
696
|
+
|
|
697
|
+
| Method | Returns | Description |
|
|
698
|
+
| --- | --- | --- |
|
|
699
|
+
| `addTask(task)` | `void` | Register a task (wires event forwarding) |
|
|
700
|
+
| `removeTask(task)` | `void` | Remove task and unsubscribe events |
|
|
701
|
+
| `getTaskByName(name)` | `Task \| undefined` | Look up by name |
|
|
702
|
+
| `triggerTaskByName(name)` | `Promise<any>` | Trigger by name |
|
|
703
|
+
| `addAndScheduleTask(task, cron)` | `void` | Register + schedule |
|
|
704
|
+
| `scheduleTaskByName(name, cron)` | `void` | Schedule existing task |
|
|
705
|
+
| `descheduleTaskByName(name)` | `void` | Remove schedule |
|
|
706
|
+
| `getTaskMetadata(name)` | `ITaskMetadata \| null` | Single task metadata |
|
|
707
|
+
| `getAllTasksMetadata()` | `ITaskMetadata[]` | All tasks metadata |
|
|
708
|
+
| `getScheduledTasks()` | `IScheduledTaskInfo[]` | Scheduled task info |
|
|
709
|
+
| `getNextScheduledRuns(limit?)` | `Array<{...}>` | Upcoming scheduled runs |
|
|
710
|
+
| `getTasksByLabel(key, value)` | `Task[]` | Filter tasks by label |
|
|
711
|
+
| `getTasksMetadataByLabel(key, value)` | `ITaskMetadata[]` | Filter metadata by label |
|
|
712
|
+
| `addExecuteRemoveTask(task, opts?)` | `Promise<ITaskExecutionReport>` | One-shot execution with report |
|
|
713
|
+
| `start()` | `Promise<void>` | Start cron + coordinator |
|
|
714
|
+
| `stop()` | `Promise<void>` | Stop cron + clean up subscriptions |
|
|
715
|
+
|
|
716
|
+
### TaskManager Properties
|
|
717
|
+
|
|
718
|
+
| Property | Type | Description |
|
|
719
|
+
| --- | --- | --- |
|
|
720
|
+
| `taskSubject` | `Subject<ITaskEvent>` | Aggregated events from all added tasks |
|
|
721
|
+
| `taskMap` | `ObjectMap<Task>` | Internal task registry |
|
|
722
|
+
|
|
723
|
+
### Exported Types
|
|
568
724
|
|
|
569
725
|
```typescript
|
|
570
|
-
import {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
726
|
+
import type {
|
|
727
|
+
ITaskMetadata,
|
|
728
|
+
ITaskExecutionReport,
|
|
729
|
+
IScheduledTaskInfo,
|
|
730
|
+
ITaskEvent,
|
|
731
|
+
TTaskEventType,
|
|
732
|
+
ITaskStep,
|
|
733
|
+
ITaskFunction,
|
|
734
|
+
StepNames,
|
|
735
|
+
} from '@push.rocks/taskbuffer';
|
|
576
736
|
```
|
|
577
737
|
|
|
578
|
-
## 📚 API Reference
|
|
579
|
-
|
|
580
|
-
### Core Classes
|
|
581
|
-
|
|
582
|
-
- **`Task<T, TSteps>`** - Basic task unit with optional step tracking
|
|
583
|
-
- **`TaskManager`** - Central orchestrator for task management
|
|
584
|
-
- **`Taskchain`** - Sequential task executor
|
|
585
|
-
- **`Taskparallel`** - Concurrent task executor
|
|
586
|
-
- **`TaskOnce`** - Single-execution task
|
|
587
|
-
- **`TaskDebounced`** - Debounced task that waits for a pause in triggers
|
|
588
|
-
- **`TaskRunner`** - Sequential task runner with scheduling support
|
|
589
|
-
- **`distributedCoordination`** - Namespace for distributed task coordination
|
|
590
|
-
|
|
591
|
-
### Key Methods
|
|
592
|
-
|
|
593
|
-
#### Task Methods
|
|
594
|
-
- `trigger(input?: T): Promise<any>` - Execute the task
|
|
595
|
-
- `notifyStep(stepName: StepNames<TSteps>): void` - Update current step
|
|
596
|
-
- `getProgress(): number` - Get progress percentage (0-100)
|
|
597
|
-
- `getStepsMetadata(): ITaskStep[]` - Get detailed step information
|
|
598
|
-
- `getMetadata(): ITaskMetadata` - Get complete task metadata
|
|
599
|
-
|
|
600
|
-
#### TaskManager Methods
|
|
601
|
-
- `addTask(task: Task): void` - Register a task
|
|
602
|
-
- `getTaskByName(name: string): Task | undefined` - Retrieve task by name
|
|
603
|
-
- `addAndScheduleTask(task: Task, cronExpression: string): void` - Schedule task
|
|
604
|
-
- `descheduleTaskByName(name: string): void` - Remove scheduling
|
|
605
|
-
- `getTaskMetadata(name: string): ITaskMetadata | null` - Get task metadata
|
|
606
|
-
- `getAllTasksMetadata(): ITaskMetadata[]` - Get all tasks metadata
|
|
607
|
-
- `getScheduledTasks(): IScheduledTaskInfo[]` - List scheduled tasks
|
|
608
|
-
- `addExecuteRemoveTask(task, options?): Promise<ITaskExecutionReport>` - Execute once
|
|
609
|
-
|
|
610
738
|
## License and Legal Information
|
|
611
739
|
|
|
612
|
-
This repository contains open-source code
|
|
740
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
613
741
|
|
|
614
742
|
**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.
|
|
615
743
|
|
|
616
744
|
### Trademarks
|
|
617
745
|
|
|
618
|
-
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein.
|
|
746
|
+
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
|
747
|
+
|
|
748
|
+
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
|
619
749
|
|
|
620
750
|
### Company Information
|
|
621
751
|
|
|
622
|
-
Task Venture Capital GmbH
|
|
623
|
-
Registered at District
|
|
752
|
+
Task Venture Capital GmbH
|
|
753
|
+
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
624
754
|
|
|
625
|
-
For any legal inquiries or
|
|
755
|
+
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
626
756
|
|
|
627
|
-
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.
|
|
757
|
+
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.
|