@push.rocks/taskbuffer 4.2.1 → 5.0.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 +2 -2
- package/dist_ts/index.js +2 -2
- package/dist_ts/taskbuffer.classes.bufferrunner.d.ts +1 -1
- package/dist_ts/taskbuffer.classes.bufferrunner.js +1 -1
- package/dist_ts/taskbuffer.classes.cyclecounter.d.ts +1 -1
- package/dist_ts/taskbuffer.classes.cyclecounter.js +1 -1
- package/dist_ts/taskbuffer.classes.task.d.ts +12 -15
- package/dist_ts/taskbuffer.classes.task.js +2 -27
- package/dist_ts/taskbuffer.classes.taskconstraintgroup.d.ts +18 -0
- package/dist_ts/taskbuffer.classes.taskconstraintgroup.js +64 -0
- package/dist_ts/taskbuffer.classes.taskmanager.d.ts +18 -9
- package/dist_ts/taskbuffer.classes.taskmanager.js +106 -7
- package/dist_ts/taskbuffer.interfaces.d.ts +12 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +1 -1
- package/readme.hints.md +29 -7
- package/readme.md +220 -61
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +2 -2
- package/ts/taskbuffer.classes.bufferrunner.ts +1 -1
- package/ts/taskbuffer.classes.cyclecounter.ts +1 -1
- package/ts/taskbuffer.classes.task.ts +18 -54
- package/ts/taskbuffer.classes.taskconstraintgroup.ts +80 -0
- package/ts/taskbuffer.classes.taskmanager.ts +155 -33
- package/ts/taskbuffer.interfaces.ts +14 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/dist_ts/taskbuffer.classes.taskrunner.d.ts +0 -30
- package/dist_ts/taskbuffer.classes.taskrunner.js +0 -60
- package/ts/taskbuffer.classes.taskrunner.ts +0 -69
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @push.rocks/taskbuffer 🚀
|
|
2
2
|
|
|
3
|
-
> **Modern TypeScript task orchestration with smart buffering, scheduling, labels, and real-time event streaming**
|
|
3
|
+
> **Modern TypeScript task orchestration with constraint-based concurrency, 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/)
|
|
@@ -13,6 +13,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
13
13
|
## 🌟 Features
|
|
14
14
|
|
|
15
15
|
- **🎯 Type-Safe Task Management** — Full TypeScript support with generics and type inference
|
|
16
|
+
- **🔒 Constraint-Based Concurrency** — Per-key mutual exclusion, group concurrency limits, and cooldown enforcement via `TaskConstraintGroup`
|
|
16
17
|
- **📊 Real-Time Progress Tracking** — Step-based progress with percentage weights
|
|
17
18
|
- **⚡ Smart Buffering** — Intelligent request debouncing and batching
|
|
18
19
|
- **⏰ Cron Scheduling** — Schedule tasks with cron expressions
|
|
@@ -49,6 +50,24 @@ const result = await greetTask.trigger('World');
|
|
|
49
50
|
console.log(result); // "Hello, World!"
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
### Task with Typed Data 📦
|
|
54
|
+
|
|
55
|
+
Every task can carry a typed data bag — perfect for constraint matching, routing, and metadata:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const task = new Task<undefined, [], { domain: string; priority: number }>({
|
|
59
|
+
name: 'update-dns',
|
|
60
|
+
data: { domain: 'example.com', priority: 1 },
|
|
61
|
+
taskFunction: async () => {
|
|
62
|
+
// task.data is fully typed here
|
|
63
|
+
console.log(`Updating DNS for ${task.data.domain}`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
task.data.domain; // string — fully typed
|
|
68
|
+
task.data.priority; // number — fully typed
|
|
69
|
+
```
|
|
70
|
+
|
|
52
71
|
### Task with Steps & Progress 📊
|
|
53
72
|
|
|
54
73
|
```typescript
|
|
@@ -84,6 +103,132 @@ console.log(deployTask.getStepsMetadata()); // Step details with status
|
|
|
84
103
|
|
|
85
104
|
> **Note:** `notifyStep()` is fully type-safe — TypeScript only accepts step names you declared in the `steps` array when you use `as const`.
|
|
86
105
|
|
|
106
|
+
## 🔒 Task Constraints — Concurrency, Mutual Exclusion & Cooldowns
|
|
107
|
+
|
|
108
|
+
`TaskConstraintGroup` is the unified mechanism for controlling how tasks run relative to each other. It replaces older patterns like task runners, blocking tasks, and execution delays with a single, composable, key-based constraint system.
|
|
109
|
+
|
|
110
|
+
### Per-Key Mutual Exclusion
|
|
111
|
+
|
|
112
|
+
Ensure only one task runs at a time for a given key (e.g. per domain, per tenant, per resource):
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { Task, TaskManager, TaskConstraintGroup } from '@push.rocks/taskbuffer';
|
|
116
|
+
|
|
117
|
+
const manager = new TaskManager();
|
|
118
|
+
|
|
119
|
+
// Only one DNS update per domain at a time
|
|
120
|
+
const domainMutex = new TaskConstraintGroup<{ domain: string }>({
|
|
121
|
+
name: 'domain-mutex',
|
|
122
|
+
maxConcurrent: 1,
|
|
123
|
+
constraintKeyForTask: (task) => task.data.domain,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
manager.addConstraintGroup(domainMutex);
|
|
127
|
+
|
|
128
|
+
const task1 = new Task<undefined, [], { domain: string }>({
|
|
129
|
+
name: 'update-a.com',
|
|
130
|
+
data: { domain: 'a.com' },
|
|
131
|
+
taskFunction: async () => { /* update DNS for a.com */ },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const task2 = new Task<undefined, [], { domain: string }>({
|
|
135
|
+
name: 'update-a.com-2',
|
|
136
|
+
data: { domain: 'a.com' },
|
|
137
|
+
taskFunction: async () => { /* another update for a.com */ },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
manager.addTask(task1);
|
|
141
|
+
manager.addTask(task2);
|
|
142
|
+
|
|
143
|
+
// task2 waits until task1 finishes (same domain key)
|
|
144
|
+
await Promise.all([
|
|
145
|
+
manager.triggerTask(task1),
|
|
146
|
+
manager.triggerTask(task2),
|
|
147
|
+
]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Group Concurrency Limits
|
|
151
|
+
|
|
152
|
+
Cap how many tasks can run concurrently across a group:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Max 3 DNS updaters running globally at once
|
|
156
|
+
const dnsLimit = new TaskConstraintGroup<{ group: string }>({
|
|
157
|
+
name: 'dns-concurrency',
|
|
158
|
+
maxConcurrent: 3,
|
|
159
|
+
constraintKeyForTask: (task) =>
|
|
160
|
+
task.data.group === 'dns' ? 'dns' : null, // null = skip constraint
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
manager.addConstraintGroup(dnsLimit);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Cooldowns (Rate Limiting)
|
|
167
|
+
|
|
168
|
+
Enforce a minimum time gap between consecutive executions for the same key:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// No more than one API call per domain every 11 seconds
|
|
172
|
+
const rateLimiter = new TaskConstraintGroup<{ domain: string }>({
|
|
173
|
+
name: 'api-rate-limit',
|
|
174
|
+
maxConcurrent: 1,
|
|
175
|
+
cooldownMs: 11000,
|
|
176
|
+
constraintKeyForTask: (task) => task.data.domain,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
manager.addConstraintGroup(rateLimiter);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Global Concurrency Cap
|
|
183
|
+
|
|
184
|
+
Limit total concurrent tasks system-wide:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const globalCap = new TaskConstraintGroup({
|
|
188
|
+
name: 'global-cap',
|
|
189
|
+
maxConcurrent: 10,
|
|
190
|
+
constraintKeyForTask: () => 'all', // same key = shared limit
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
manager.addConstraintGroup(globalCap);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Composing Multiple Constraints
|
|
197
|
+
|
|
198
|
+
Multiple constraint groups stack — a task only runs when **all** applicable constraints allow it:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
manager.addConstraintGroup(globalCap); // max 10 globally
|
|
202
|
+
manager.addConstraintGroup(domainMutex); // max 1 per domain
|
|
203
|
+
manager.addConstraintGroup(rateLimiter); // 11s cooldown per domain
|
|
204
|
+
|
|
205
|
+
// A task must satisfy ALL three constraints before it starts
|
|
206
|
+
await manager.triggerTask(dnsTask);
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Selective Constraints
|
|
210
|
+
|
|
211
|
+
Return `null` from `constraintKeyForTask` to exempt a task from a constraint group:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const constraint = new TaskConstraintGroup<{ priority: string }>({
|
|
215
|
+
name: 'low-priority-limit',
|
|
216
|
+
maxConcurrent: 2,
|
|
217
|
+
constraintKeyForTask: (task) =>
|
|
218
|
+
task.data.priority === 'low' ? 'low-priority' : null, // high priority tasks skip this constraint
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### How It Works
|
|
223
|
+
|
|
224
|
+
When you trigger a task through `TaskManager` (via `triggerTask`, `triggerTaskByName`, `addExecuteRemoveTask`, or cron), the manager:
|
|
225
|
+
|
|
226
|
+
1. Evaluates all registered constraint groups against the task
|
|
227
|
+
2. If no constraints apply (all matchers return `null`) → runs immediately
|
|
228
|
+
3. If all applicable constraints have capacity → acquires slots and runs
|
|
229
|
+
4. If any constraint blocks → enqueues the task; when a running task completes, the queue is drained
|
|
230
|
+
5. Cooldown-blocked tasks auto-retry after the shortest remaining cooldown expires
|
|
231
|
+
|
|
87
232
|
## 🎯 Core Concepts
|
|
88
233
|
|
|
89
234
|
### Task Buffering — Intelligent Request Management
|
|
@@ -95,7 +240,6 @@ const apiTask = new Task({
|
|
|
95
240
|
name: 'APIRequest',
|
|
96
241
|
buffered: true,
|
|
97
242
|
bufferMax: 5, // Maximum 5 concurrent executions
|
|
98
|
-
execDelay: 100, // Minimum 100ms between executions
|
|
99
243
|
taskFunction: async (endpoint) => {
|
|
100
244
|
return await fetch(endpoint).then((r) => r.json());
|
|
101
245
|
},
|
|
@@ -156,9 +300,9 @@ console.log(`Saved ${savedCount} items`);
|
|
|
156
300
|
Taskchain also supports dynamic mutation:
|
|
157
301
|
|
|
158
302
|
```typescript
|
|
159
|
-
pipeline.addTask(newTask);
|
|
160
|
-
pipeline.removeTask(oldTask);
|
|
161
|
-
pipeline.shiftTask();
|
|
303
|
+
pipeline.addTask(newTask); // Append to chain
|
|
304
|
+
pipeline.removeTask(oldTask); // Remove by reference (returns boolean)
|
|
305
|
+
pipeline.shiftTask(); // Remove & return first task
|
|
162
306
|
```
|
|
163
307
|
|
|
164
308
|
Error context is rich — a chain failure includes the chain name, failing task name, task index, and preserves the original error as `.cause`.
|
|
@@ -220,27 +364,6 @@ await initTask.trigger(); // No-op
|
|
|
220
364
|
console.log(initTask.hasTriggered); // true
|
|
221
365
|
```
|
|
222
366
|
|
|
223
|
-
### TaskRunner — Managed Queue with Concurrency Control
|
|
224
|
-
|
|
225
|
-
Process a queue of tasks with a configurable parallelism limit:
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
import { TaskRunner } from '@push.rocks/taskbuffer';
|
|
229
|
-
|
|
230
|
-
const runner = new TaskRunner();
|
|
231
|
-
runner.setMaxParallelJobs(3); // Run up to 3 tasks concurrently
|
|
232
|
-
|
|
233
|
-
await runner.start();
|
|
234
|
-
|
|
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();
|
|
242
|
-
```
|
|
243
|
-
|
|
244
367
|
## 🏷️ Labels — Multi-Tenant Task Filtering
|
|
245
368
|
|
|
246
369
|
Attach arbitrary key-value labels to any task for filtering, grouping, or multi-tenant isolation:
|
|
@@ -411,6 +534,10 @@ manager.addTask(deployTask);
|
|
|
411
534
|
manager.addAndScheduleTask(backupTask, '0 2 * * *'); // Daily at 2 AM
|
|
412
535
|
manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes
|
|
413
536
|
|
|
537
|
+
// Register constraint groups
|
|
538
|
+
manager.addConstraintGroup(globalCap);
|
|
539
|
+
manager.addConstraintGroup(perDomainMutex);
|
|
540
|
+
|
|
414
541
|
// Query metadata
|
|
415
542
|
const meta = manager.getTaskMetadata('Deploy');
|
|
416
543
|
console.log(meta);
|
|
@@ -433,7 +560,7 @@ const allMeta = manager.getAllTasksMetadata();
|
|
|
433
560
|
const scheduled = manager.getScheduledTasks();
|
|
434
561
|
const nextRuns = manager.getNextScheduledRuns(5);
|
|
435
562
|
|
|
436
|
-
// Trigger by name
|
|
563
|
+
// Trigger by name (routes through constraints)
|
|
437
564
|
await manager.triggerTaskByName('Deploy');
|
|
438
565
|
|
|
439
566
|
// One-shot: add, execute, collect report, remove
|
|
@@ -462,6 +589,12 @@ manager.removeTask(task); // Removes from map and unsubscribes event forwarding
|
|
|
462
589
|
manager.descheduleTaskByName('Deploy'); // Remove cron schedule only
|
|
463
590
|
```
|
|
464
591
|
|
|
592
|
+
### Remove Constraint Groups
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
manager.removeConstraintGroup('domain-mutex'); // By name
|
|
596
|
+
```
|
|
597
|
+
|
|
465
598
|
## 🎨 Web Component Dashboard
|
|
466
599
|
|
|
467
600
|
Visualize your tasks in real-time with the included Lit-based web component:
|
|
@@ -570,32 +703,6 @@ await task.trigger('SELECT * FROM users'); // Setup runs here
|
|
|
570
703
|
await task.trigger('SELECT * FROM orders'); // Setup skipped, pool reused
|
|
571
704
|
```
|
|
572
705
|
|
|
573
|
-
### Blocking Tasks
|
|
574
|
-
|
|
575
|
-
Make one task wait for another to finish before executing:
|
|
576
|
-
|
|
577
|
-
```typescript
|
|
578
|
-
const initTask = new Task({
|
|
579
|
-
name: 'Init',
|
|
580
|
-
taskFunction: async () => {
|
|
581
|
-
await initializeSystem();
|
|
582
|
-
},
|
|
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
|
|
597
|
-
```
|
|
598
|
-
|
|
599
706
|
### Database Migration Pipeline
|
|
600
707
|
|
|
601
708
|
```typescript
|
|
@@ -616,15 +723,24 @@ try {
|
|
|
616
723
|
|
|
617
724
|
### Multi-Tenant SaaS Monitoring
|
|
618
725
|
|
|
619
|
-
Combine labels + events for a real-time multi-tenant
|
|
726
|
+
Combine labels + events + constraints for a real-time multi-tenant system:
|
|
620
727
|
|
|
621
728
|
```typescript
|
|
622
729
|
const manager = new TaskManager();
|
|
623
730
|
|
|
731
|
+
// Per-tenant concurrency limit
|
|
732
|
+
const tenantLimit = new TaskConstraintGroup<{ tenantId: string }>({
|
|
733
|
+
name: 'tenant-concurrency',
|
|
734
|
+
maxConcurrent: 2,
|
|
735
|
+
constraintKeyForTask: (task) => task.data.tenantId,
|
|
736
|
+
});
|
|
737
|
+
manager.addConstraintGroup(tenantLimit);
|
|
738
|
+
|
|
624
739
|
// Create tenant-scoped tasks
|
|
625
740
|
function createTenantTask(tenantId: string, taskName: string, fn: () => Promise<any>) {
|
|
626
|
-
const task = new Task({
|
|
741
|
+
const task = new Task<undefined, [], { tenantId: string }>({
|
|
627
742
|
name: `${tenantId}:${taskName}`,
|
|
743
|
+
data: { tenantId },
|
|
628
744
|
labels: { tenantId },
|
|
629
745
|
taskFunction: fn,
|
|
630
746
|
});
|
|
@@ -653,15 +769,31 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
|
653
769
|
|
|
654
770
|
| Class | Description |
|
|
655
771
|
| --- | --- |
|
|
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 |
|
|
772
|
+
| `Task<T, TSteps, TData>` | Core task unit with typed data, optional step tracking, labels, and event streaming |
|
|
773
|
+
| `TaskManager` | Centralized orchestrator with constraint groups, scheduling, label queries, and aggregated events |
|
|
774
|
+
| `TaskConstraintGroup<TData>` | Concurrency, mutual exclusion, and cooldown constraints with key-based grouping |
|
|
658
775
|
| `Taskchain` | Sequential task executor with data flow between tasks |
|
|
659
776
|
| `Taskparallel` | Concurrent task executor via `Promise.all()` |
|
|
660
777
|
| `TaskOnce` | Single-execution guard |
|
|
661
778
|
| `TaskDebounced` | Debounced task using rxjs |
|
|
662
|
-
| `TaskRunner` | Sequential queue with configurable parallelism |
|
|
663
779
|
| `TaskStep` | Step tracking unit (internal, exposed via metadata) |
|
|
664
780
|
|
|
781
|
+
### Task Constructor Options
|
|
782
|
+
|
|
783
|
+
| Option | Type | Default | Description |
|
|
784
|
+
| --- | --- | --- | --- |
|
|
785
|
+
| `taskFunction` | `ITaskFunction<T>` | *required* | The async function to execute |
|
|
786
|
+
| `name` | `string` | — | Task identifier (required for TaskManager) |
|
|
787
|
+
| `data` | `TData` | `{}` | Typed data bag for constraint matching and routing |
|
|
788
|
+
| `steps` | `ReadonlyArray<{name, description, percentage}>` | — | Step definitions for progress tracking |
|
|
789
|
+
| `buffered` | `boolean` | — | Enable request buffering |
|
|
790
|
+
| `bufferMax` | `number` | — | Max buffered calls |
|
|
791
|
+
| `preTask` | `Task \| () => Task` | — | Task to run before |
|
|
792
|
+
| `afterTask` | `Task \| () => Task` | — | Task to run after |
|
|
793
|
+
| `taskSetup` | `() => Promise<T>` | — | One-time setup function |
|
|
794
|
+
| `catchErrors` | `boolean` | `false` | Swallow errors instead of rejecting |
|
|
795
|
+
| `labels` | `Record<string, string>` | `{}` | Initial labels |
|
|
796
|
+
|
|
665
797
|
### Task Methods
|
|
666
798
|
|
|
667
799
|
| Method | Returns | Description |
|
|
@@ -682,6 +814,7 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
|
682
814
|
| Property | Type | Description |
|
|
683
815
|
| --- | --- | --- |
|
|
684
816
|
| `name` | `string` | Task identifier |
|
|
817
|
+
| `data` | `TData` | Typed data bag |
|
|
685
818
|
| `running` | `boolean` | Whether the task is currently executing |
|
|
686
819
|
| `idle` | `boolean` | Inverse of `running` |
|
|
687
820
|
| `labels` | `Record<string, string>` | Attached labels |
|
|
@@ -690,7 +823,27 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
|
690
823
|
| `errorCount` | `number` | Total error count across all runs |
|
|
691
824
|
| `runCount` | `number` | Total execution count |
|
|
692
825
|
| `lastRun` | `Date \| undefined` | Timestamp of last execution |
|
|
693
|
-
|
|
826
|
+
|
|
827
|
+
### TaskConstraintGroup Constructor Options
|
|
828
|
+
|
|
829
|
+
| Option | Type | Default | Description |
|
|
830
|
+
| --- | --- | --- | --- |
|
|
831
|
+
| `name` | `string` | *required* | Constraint group identifier |
|
|
832
|
+
| `constraintKeyForTask` | `(task) => string \| null` | *required* | Returns key for grouping, or `null` to skip |
|
|
833
|
+
| `maxConcurrent` | `number` | `Infinity` | Max concurrent tasks per key |
|
|
834
|
+
| `cooldownMs` | `number` | `0` | Minimum ms between completions per key |
|
|
835
|
+
|
|
836
|
+
### TaskConstraintGroup Methods
|
|
837
|
+
|
|
838
|
+
| Method | Returns | Description |
|
|
839
|
+
| --- | --- | --- |
|
|
840
|
+
| `getConstraintKey(task)` | `string \| null` | Get the constraint key for a task |
|
|
841
|
+
| `canRun(key)` | `boolean` | Check if a slot is available |
|
|
842
|
+
| `acquireSlot(key)` | `void` | Claim a running slot |
|
|
843
|
+
| `releaseSlot(key)` | `void` | Release a slot and record completion time |
|
|
844
|
+
| `getCooldownRemaining(key)` | `number` | Milliseconds until cooldown expires |
|
|
845
|
+
| `getRunningCount(key)` | `number` | Current running count for key |
|
|
846
|
+
| `reset()` | `void` | Clear all state |
|
|
694
847
|
|
|
695
848
|
### TaskManager Methods
|
|
696
849
|
|
|
@@ -699,7 +852,11 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
|
699
852
|
| `addTask(task)` | `void` | Register a task (wires event forwarding) |
|
|
700
853
|
| `removeTask(task)` | `void` | Remove task and unsubscribe events |
|
|
701
854
|
| `getTaskByName(name)` | `Task \| undefined` | Look up by name |
|
|
702
|
-
| `triggerTaskByName(name)` | `Promise<any>` | Trigger by name |
|
|
855
|
+
| `triggerTaskByName(name)` | `Promise<any>` | Trigger by name (routes through constraints) |
|
|
856
|
+
| `triggerTask(task)` | `Promise<any>` | Trigger directly (routes through constraints) |
|
|
857
|
+
| `triggerTaskConstrained(task, input?)` | `Promise<any>` | Core constraint evaluation entry point |
|
|
858
|
+
| `addConstraintGroup(group)` | `void` | Register a constraint group |
|
|
859
|
+
| `removeConstraintGroup(name)` | `void` | Remove a constraint group by name |
|
|
703
860
|
| `addAndScheduleTask(task, cron)` | `void` | Register + schedule |
|
|
704
861
|
| `scheduleTaskByName(name, cron)` | `void` | Schedule existing task |
|
|
705
862
|
| `descheduleTaskByName(name)` | `void` | Remove schedule |
|
|
@@ -719,6 +876,7 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
|
|
|
719
876
|
| --- | --- | --- |
|
|
720
877
|
| `taskSubject` | `Subject<ITaskEvent>` | Aggregated events from all added tasks |
|
|
721
878
|
| `taskMap` | `ObjectMap<Task>` | Internal task registry |
|
|
879
|
+
| `constraintGroups` | `TaskConstraintGroup[]` | Registered constraint groups |
|
|
722
880
|
|
|
723
881
|
### Exported Types
|
|
724
882
|
|
|
@@ -731,13 +889,14 @@ import type {
|
|
|
731
889
|
TTaskEventType,
|
|
732
890
|
ITaskStep,
|
|
733
891
|
ITaskFunction,
|
|
892
|
+
ITaskConstraintGroupOptions,
|
|
734
893
|
StepNames,
|
|
735
894
|
} from '@push.rocks/taskbuffer';
|
|
736
895
|
```
|
|
737
896
|
|
|
738
897
|
## License and Legal Information
|
|
739
898
|
|
|
740
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./
|
|
899
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
741
900
|
|
|
742
901
|
**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.
|
|
743
902
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/taskbuffer',
|
|
6
|
-
version: '
|
|
6
|
+
version: '5.0.0',
|
|
7
7
|
description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.'
|
|
8
8
|
}
|
package/ts/index.ts
CHANGED
|
@@ -4,15 +4,15 @@ export { Taskchain } from './taskbuffer.classes.taskchain.js';
|
|
|
4
4
|
export { Taskparallel } from './taskbuffer.classes.taskparallel.js';
|
|
5
5
|
export { TaskManager } from './taskbuffer.classes.taskmanager.js';
|
|
6
6
|
export { TaskOnce } from './taskbuffer.classes.taskonce.js';
|
|
7
|
-
export { TaskRunner } from './taskbuffer.classes.taskrunner.js';
|
|
8
7
|
export { TaskDebounced } from './taskbuffer.classes.taskdebounced.js';
|
|
8
|
+
export { TaskConstraintGroup } from './taskbuffer.classes.taskconstraintgroup.js';
|
|
9
9
|
|
|
10
10
|
// Task step system
|
|
11
11
|
export { TaskStep } from './taskbuffer.classes.taskstep.js';
|
|
12
12
|
export type { ITaskStep } from './taskbuffer.classes.taskstep.js';
|
|
13
13
|
|
|
14
14
|
// Metadata interfaces
|
|
15
|
-
export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, TTaskEventType } from './taskbuffer.interfaces.js';
|
|
15
|
+
export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, TTaskEventType, ITaskConstraintGroupOptions } from './taskbuffer.interfaces.js';
|
|
16
16
|
|
|
17
17
|
import * as distributedCoordination from './taskbuffer.classes.distributedcoordinator.js';
|
|
18
18
|
export { distributedCoordination };
|
|
@@ -9,7 +9,7 @@ export interface ICycleObject {
|
|
|
9
9
|
export class CycleCounter {
|
|
10
10
|
public task: Task;
|
|
11
11
|
public cycleObjectArray: ICycleObject[] = [];
|
|
12
|
-
constructor(taskArg: Task<any>) {
|
|
12
|
+
constructor(taskArg: Task<any, any, any>) {
|
|
13
13
|
this.task = taskArg;
|
|
14
14
|
}
|
|
15
15
|
public getPromiseForCycle(cycleCountArg: number) {
|
|
@@ -19,18 +19,18 @@ export type TPreOrAfterTaskFunction = () => Task<any>;
|
|
|
19
19
|
// Type helper to extract step names from array
|
|
20
20
|
export type StepNames<T> = T extends ReadonlyArray<{ name: infer N }> ? N : never;
|
|
21
21
|
|
|
22
|
-
export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []> {
|
|
22
|
+
export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = [], TData extends Record<string, unknown> = Record<string, unknown>> {
|
|
23
23
|
public static extractTask<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
|
|
24
|
-
preOrAfterTaskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
|
|
25
|
-
): Task<T, TSteps> {
|
|
24
|
+
preOrAfterTaskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
|
|
25
|
+
): Task<T, TSteps, any> {
|
|
26
26
|
switch (true) {
|
|
27
27
|
case !preOrAfterTaskArg:
|
|
28
28
|
return null;
|
|
29
29
|
case preOrAfterTaskArg instanceof Task:
|
|
30
|
-
return preOrAfterTaskArg as Task<T, TSteps>;
|
|
30
|
+
return preOrAfterTaskArg as Task<T, TSteps, any>;
|
|
31
31
|
case typeof preOrAfterTaskArg === 'function':
|
|
32
32
|
const taskFunction = preOrAfterTaskArg as TPreOrAfterTaskFunction;
|
|
33
|
-
return taskFunction() as unknown as Task<T, TSteps>;
|
|
33
|
+
return taskFunction() as unknown as Task<T, TSteps, any>;
|
|
34
34
|
default:
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
@@ -42,7 +42,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
42
42
|
return done.promise;
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
public static isTask = (taskArg: Task<any>): boolean => {
|
|
45
|
+
public static isTask = (taskArg: Task<any, any, any>): boolean => {
|
|
46
46
|
if (taskArg instanceof Task && typeof taskArg.taskFunction === 'function') {
|
|
47
47
|
return true;
|
|
48
48
|
} else {
|
|
@@ -51,8 +51,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
public static isTaskTouched<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
|
|
54
|
-
taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
|
|
55
|
-
touchedTasksArray: Task<T, TSteps>[],
|
|
54
|
+
taskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
|
|
55
|
+
touchedTasksArray: Task<T, TSteps, any>[],
|
|
56
56
|
): boolean {
|
|
57
57
|
const taskToCheck = Task.extractTask(taskArg);
|
|
58
58
|
let result = false;
|
|
@@ -65,25 +65,16 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
public static runTask = async <T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
|
|
68
|
-
taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
|
|
69
|
-
optionsArg: { x?: any; touchedTasksArray?: Task<T, TSteps>[] },
|
|
68
|
+
taskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
|
|
69
|
+
optionsArg: { x?: any; touchedTasksArray?: Task<T, TSteps, any>[] },
|
|
70
70
|
) => {
|
|
71
71
|
const taskToRun = Task.extractTask(taskArg);
|
|
72
72
|
const done = plugins.smartpromise.defer();
|
|
73
73
|
|
|
74
|
-
// Wait for all blocking tasks to finish
|
|
75
|
-
for (const task of taskToRun.blockingTasks) {
|
|
76
|
-
await task.finished;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
74
|
if (!taskToRun.setupValue && taskToRun.taskSetup) {
|
|
80
75
|
taskToRun.setupValue = await taskToRun.taskSetup();
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
if (taskToRun.execDelay) {
|
|
84
|
-
await plugins.smartdelay.delayFor(taskToRun.execDelay);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
78
|
taskToRun.running = true;
|
|
88
79
|
taskToRun.runCount++;
|
|
89
80
|
taskToRun.lastRun = new Date();
|
|
@@ -100,26 +91,10 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
100
91
|
// Complete all steps when task finishes
|
|
101
92
|
taskToRun.completeAllSteps();
|
|
102
93
|
taskToRun.emitEvent(taskToRun.lastError ? 'failed' : 'completed');
|
|
103
|
-
|
|
104
|
-
// When the task has finished running, resolve the finished promise
|
|
105
|
-
taskToRun.resolveFinished();
|
|
106
|
-
|
|
107
|
-
// Create a new finished promise for the next run
|
|
108
|
-
taskToRun.finished = new Promise((resolve) => {
|
|
109
|
-
taskToRun.resolveFinished = resolve;
|
|
110
|
-
});
|
|
111
94
|
})
|
|
112
95
|
.catch((err) => {
|
|
113
96
|
taskToRun.running = false;
|
|
114
97
|
taskToRun.emitEvent('failed', { error: err instanceof Error ? err.message : String(err) });
|
|
115
|
-
|
|
116
|
-
// Resolve finished so blocking dependants don't hang
|
|
117
|
-
taskToRun.resolveFinished();
|
|
118
|
-
|
|
119
|
-
// Create a new finished promise for the next run
|
|
120
|
-
taskToRun.finished = new Promise((resolve) => {
|
|
121
|
-
taskToRun.resolveFinished = resolve;
|
|
122
|
-
});
|
|
123
98
|
});
|
|
124
99
|
|
|
125
100
|
const options = {
|
|
@@ -127,7 +102,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
127
102
|
...optionsArg,
|
|
128
103
|
};
|
|
129
104
|
const x = options.x;
|
|
130
|
-
const touchedTasksArray: Task<T, TSteps>[] = options.touchedTasksArray;
|
|
105
|
+
const touchedTasksArray: Task<T, TSteps, any>[] = options.touchedTasksArray;
|
|
131
106
|
|
|
132
107
|
touchedTasksArray.push(taskToRun);
|
|
133
108
|
|
|
@@ -198,18 +173,12 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
198
173
|
public cronJob: plugins.smarttime.CronJob;
|
|
199
174
|
|
|
200
175
|
public bufferMax: number;
|
|
201
|
-
public execDelay: number;
|
|
202
176
|
public timeout: number;
|
|
203
177
|
|
|
204
|
-
public
|
|
205
|
-
public afterTask: Task<T, any> | TPreOrAfterTaskFunction;
|
|
178
|
+
public data: TData;
|
|
206
179
|
|
|
207
|
-
|
|
208
|
-
public
|
|
209
|
-
|
|
210
|
-
// Add a promise that will resolve when the task has finished
|
|
211
|
-
private finished: Promise<void>;
|
|
212
|
-
private resolveFinished: () => void;
|
|
180
|
+
public preTask: Task<T, any, any> | TPreOrAfterTaskFunction;
|
|
181
|
+
public afterTask: Task<T, any, any> | TPreOrAfterTaskFunction;
|
|
213
182
|
|
|
214
183
|
public running: boolean = false;
|
|
215
184
|
public bufferRunner = new BufferRunner(this);
|
|
@@ -275,12 +244,12 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
275
244
|
|
|
276
245
|
constructor(optionsArg: {
|
|
277
246
|
taskFunction: ITaskFunction<T>;
|
|
278
|
-
preTask?: Task<T, any> | TPreOrAfterTaskFunction;
|
|
279
|
-
afterTask?: Task<T, any> | TPreOrAfterTaskFunction;
|
|
247
|
+
preTask?: Task<T, any, any> | TPreOrAfterTaskFunction;
|
|
248
|
+
afterTask?: Task<T, any, any> | TPreOrAfterTaskFunction;
|
|
280
249
|
buffered?: boolean;
|
|
281
250
|
bufferMax?: number;
|
|
282
|
-
execDelay?: number;
|
|
283
251
|
name?: string;
|
|
252
|
+
data?: TData;
|
|
284
253
|
taskSetup?: ITaskSetupFunction<T>;
|
|
285
254
|
steps?: TSteps;
|
|
286
255
|
catchErrors?: boolean;
|
|
@@ -291,8 +260,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
291
260
|
this.afterTask = optionsArg.afterTask;
|
|
292
261
|
this.buffered = optionsArg.buffered;
|
|
293
262
|
this.bufferMax = optionsArg.bufferMax;
|
|
294
|
-
this.execDelay = optionsArg.execDelay;
|
|
295
263
|
this.name = optionsArg.name;
|
|
264
|
+
this.data = optionsArg.data ?? ({} as TData);
|
|
296
265
|
this.taskSetup = optionsArg.taskSetup;
|
|
297
266
|
this.catchErrors = optionsArg.catchErrors ?? false;
|
|
298
267
|
this.labels = optionsArg.labels ? { ...optionsArg.labels } : {};
|
|
@@ -309,11 +278,6 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
|
|
|
309
278
|
this.steps.set(stepConfig.name, step);
|
|
310
279
|
}
|
|
311
280
|
}
|
|
312
|
-
|
|
313
|
-
// Create the finished promise
|
|
314
|
-
this.finished = new Promise((resolve) => {
|
|
315
|
-
this.resolveFinished = resolve;
|
|
316
|
-
});
|
|
317
281
|
}
|
|
318
282
|
|
|
319
283
|
public trigger(x?: any): Promise<any> {
|