@qianxude/tem 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -38
- package/package.json +3 -1
- package/src/cli/README.md +218 -0
- package/src/core/tem.ts +29 -1
- package/src/core/worker.ts +78 -6
- package/src/database/index.ts +47 -7
- package/src/index.ts +1 -1
- package/src/interfaces/index.ts +60 -0
- package/src/mock-server/README.md +180 -13
- package/src/services/batch-interruption.ts +192 -0
- package/src/services/batch.ts +32 -4
- package/src/services/index.ts +1 -0
- package/src/utils/auto-detect.ts +5 -2
package/README.md
CHANGED
|
@@ -6,6 +6,14 @@ Built for **single-process, IO-bound scenarios** where you need reliable task ex
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bun add @qianxude/tem
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
9
17
|
## Features
|
|
10
18
|
|
|
11
19
|
- **SQLite Persistence** — Tasks survive process restarts using `bun:sqlite` with WAL mode
|
|
@@ -43,38 +51,37 @@ Don't use tem when you need:
|
|
|
43
51
|
```typescript
|
|
44
52
|
import { TEM } from "@qianxude/tem";
|
|
45
53
|
|
|
46
|
-
// Initialize
|
|
47
54
|
const tem = new TEM({
|
|
48
|
-
|
|
49
|
-
concurrency: 5,
|
|
50
|
-
|
|
51
|
-
rateLimit: {
|
|
52
|
-
perMinute: 60, // Respect LLM provider limits
|
|
53
|
-
perSecond: 5
|
|
54
|
-
}
|
|
55
|
+
databasePath: "./tem.db",
|
|
56
|
+
concurrency: 5,
|
|
57
|
+
pollIntervalMs: 1000,
|
|
58
|
+
rateLimit: { requests: 60, windowMs: 60000 } // 60 req/min
|
|
55
59
|
});
|
|
56
60
|
|
|
57
61
|
// Create a batch
|
|
58
62
|
const batch = await tem.batch.create({
|
|
59
|
-
code: "2026-02-15-llm-fix",
|
|
63
|
+
code: "2026-02-15-llm-fix",
|
|
60
64
|
type: "rewrite-docs"
|
|
61
65
|
});
|
|
62
66
|
|
|
63
|
-
//
|
|
64
|
-
await tem.task.
|
|
67
|
+
// Create tasks
|
|
68
|
+
await tem.task.createMany([
|
|
65
69
|
{ batchId: batch.id, type: "rewrite", payload: { docId: 1 } },
|
|
66
70
|
{ batchId: batch.id, type: "rewrite", payload: { docId: 2 } },
|
|
67
71
|
{ batchId: batch.id, type: "rewrite", payload: { docId: 3 } }
|
|
68
72
|
]);
|
|
69
73
|
|
|
70
|
-
// Register handler
|
|
71
|
-
tem.worker.register("rewrite", async (
|
|
72
|
-
const result = await callLLM(
|
|
74
|
+
// Register handler — payload is your task data, context has metadata
|
|
75
|
+
tem.worker.register("rewrite", async (payload, context) => {
|
|
76
|
+
const result = await callLLM(payload);
|
|
73
77
|
return result; // Stored in task.result
|
|
74
78
|
});
|
|
75
79
|
|
|
76
80
|
// Start processing
|
|
77
81
|
tem.worker.start();
|
|
82
|
+
|
|
83
|
+
// Stop when done
|
|
84
|
+
await tem.stop();
|
|
78
85
|
```
|
|
79
86
|
|
|
80
87
|
---
|
|
@@ -99,6 +106,111 @@ failed
|
|
|
99
106
|
|
|
100
107
|
---
|
|
101
108
|
|
|
109
|
+
## Core Concepts
|
|
110
|
+
|
|
111
|
+
- **Batch** — A named group of tasks. All recovery operations (resume, retry) work at batch level.
|
|
112
|
+
- **Task** — A unit of work with a `type`, opaque `payload`, and tracked `status`.
|
|
113
|
+
- **Worker** — Polls for pending tasks and dispatches them to registered handlers by type.
|
|
114
|
+
- **Payload** — Opaque JSON; the framework never parses it. Your handler receives it as-is.
|
|
115
|
+
- **Claim model** — Tasks are acquired atomically (`UPDATE ... WHERE status='pending'`), preventing duplicate execution.
|
|
116
|
+
|
|
117
|
+
### Task Ordering
|
|
118
|
+
|
|
119
|
+
Tasks within a batch are claimed and executed in **FIFO order** (First-In-First-Out) based on creation time.
|
|
120
|
+
When multiple tasks are pending, the task created first will be claimed first:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// These tasks will be claimed in order: task1, then task2, then task3
|
|
124
|
+
await tem.task.create({ batchId: batch.id, type: "process", payload: { id: 1 } }); // task1
|
|
125
|
+
await tem.task.create({ batchId: batch.id, type: "process", payload: { id: 2 } }); // task2
|
|
126
|
+
await tem.task.create({ batchId: batch.id, type: "process", payload: { id: 3 } }); // task3
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Error Handling
|
|
132
|
+
|
|
133
|
+
By default, any thrown error causes the task to retry up to `defaultMaxAttempts`:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
tem.worker.register("process", async (payload, context) => {
|
|
137
|
+
console.log(`Attempt ${context.attempt}`);
|
|
138
|
+
const result = await callAPI(payload); // throws → auto-retry
|
|
139
|
+
return result;
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For permanent failures that should not be retried, throw `NonRetryableError`:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { TEM, NonRetryableError } from "@qianxude/tem";
|
|
147
|
+
|
|
148
|
+
tem.worker.register("validate", async (payload) => {
|
|
149
|
+
if (!payload.id) {
|
|
150
|
+
throw new NonRetryableError("Missing required field: id");
|
|
151
|
+
// Task goes directly to 'failed', no retries
|
|
152
|
+
}
|
|
153
|
+
return process(payload);
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Batch Interruption
|
|
160
|
+
|
|
161
|
+
Automatically stop a batch when error thresholds are exceeded:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const batch = await tem.batch.create({
|
|
165
|
+
code: "llm-run-01",
|
|
166
|
+
type: "summarize",
|
|
167
|
+
interruptionCriteria: {
|
|
168
|
+
maxErrorRate: 0.3, // Stop if >30% tasks fail
|
|
169
|
+
maxFailedTasks: 10, // Stop if >10 tasks fail
|
|
170
|
+
maxConsecutiveFailures: 5, // Stop if 5 failures in a row
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Check interruption details after the batch stops:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const logs = await tem.interruption.getInterruptionLog(batchId);
|
|
179
|
+
// [{ reason, message, statsAtInterruption }]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Manually interrupt a running batch:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
await tem.interruptBatch(batchId, "manual", "Stopping due to bad data");
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Auto-Detect Constraints
|
|
191
|
+
|
|
192
|
+
Probe an API endpoint to discover its concurrency and rate limits before running tasks:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const config = await TEM.detectConstraints({
|
|
196
|
+
url: "https://api.example.com/v1/endpoint",
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { Authorization: "Bearer " + process.env.API_KEY },
|
|
199
|
+
body: { /* minimal valid request */ },
|
|
200
|
+
timeoutMs: 30000,
|
|
201
|
+
maxConcurrencyToTest: 50,
|
|
202
|
+
rateLimitTestDurationMs: 10000,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const tem = new TEM({
|
|
206
|
+
databasePath: "./tasks.db",
|
|
207
|
+
concurrency: config.concurrency,
|
|
208
|
+
rateLimit: config.rateLimit,
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
102
214
|
## Recovery Patterns
|
|
103
215
|
|
|
104
216
|
### Resume After Crash
|
|
@@ -133,7 +245,7 @@ TEM
|
|
|
133
245
|
├── Worker # Execution loop with concurrency/rate limiting
|
|
134
246
|
├── ConcurrencyController # Semaphore for local concurrency
|
|
135
247
|
├── RateLimiter # Token bucket for API rate limits
|
|
136
|
-
└──
|
|
248
|
+
└── BatchInterruptionService # Auto-stop on error thresholds
|
|
137
249
|
```
|
|
138
250
|
|
|
139
251
|
### Why Claim-Based?
|
|
@@ -199,12 +311,13 @@ This ensures:
|
|
|
199
311
|
|
|
200
312
|
```typescript
|
|
201
313
|
interface TEMConfig {
|
|
202
|
-
|
|
203
|
-
concurrency?: number;
|
|
204
|
-
|
|
314
|
+
databasePath: string; // SQLite file path
|
|
315
|
+
concurrency?: number; // Default: 5
|
|
316
|
+
pollIntervalMs?: number; // Default: 1000ms
|
|
317
|
+
defaultMaxAttempts?: number; // Default: 3
|
|
205
318
|
rateLimit?: {
|
|
206
|
-
|
|
207
|
-
|
|
319
|
+
requests: number; // Number of requests
|
|
320
|
+
windowMs: number; // Time window in ms (e.g. 60000 for per-minute)
|
|
208
321
|
};
|
|
209
322
|
}
|
|
210
323
|
```
|
|
@@ -216,57 +329,71 @@ interface TEMConfig {
|
|
|
216
329
|
const batch = await tem.batch.create({
|
|
217
330
|
code: "unique-batch-code",
|
|
218
331
|
type: "batch-type",
|
|
219
|
-
metadata?: { ... }
|
|
332
|
+
metadata?: { ... },
|
|
333
|
+
interruptionCriteria?: {
|
|
334
|
+
maxErrorRate?: number;
|
|
335
|
+
maxFailedTasks?: number;
|
|
336
|
+
maxConsecutiveFailures?: number;
|
|
337
|
+
}
|
|
220
338
|
});
|
|
221
339
|
|
|
222
|
-
// Get batch
|
|
223
|
-
const batch = await tem.batch.
|
|
224
|
-
|
|
225
|
-
// List batches
|
|
226
|
-
const batches = await tem.batch.list({ type?: "..." });
|
|
340
|
+
// Get batch by ID
|
|
341
|
+
const batch = await tem.batch.getById(batchId);
|
|
227
342
|
|
|
228
343
|
// Get statistics
|
|
229
344
|
const stats = await tem.batch.getStats(batchId);
|
|
230
|
-
// { pending
|
|
345
|
+
// { pending, running, completed, failed, total }
|
|
231
346
|
|
|
232
347
|
// Resume after crash (running → pending)
|
|
233
348
|
await tem.batch.resume(batchId);
|
|
234
349
|
|
|
235
|
-
// Retry all failed (failed → pending, attempt
|
|
350
|
+
// Retry all failed (failed → pending, attempt reset)
|
|
236
351
|
await tem.batch.retryFailed(batchId);
|
|
237
352
|
```
|
|
238
353
|
|
|
239
354
|
### Task Operations
|
|
240
355
|
|
|
241
356
|
```typescript
|
|
242
|
-
//
|
|
243
|
-
await tem.task.
|
|
357
|
+
// Create single task
|
|
358
|
+
await tem.task.create({
|
|
244
359
|
batchId: string,
|
|
245
360
|
type: string,
|
|
246
361
|
payload: object,
|
|
247
|
-
|
|
362
|
+
maxAttempts?: number
|
|
248
363
|
});
|
|
249
364
|
|
|
250
|
-
// Bulk
|
|
251
|
-
await tem.task.
|
|
365
|
+
// Bulk create (single transaction)
|
|
366
|
+
await tem.task.createMany([
|
|
252
367
|
{ batchId, type, payload },
|
|
253
368
|
...
|
|
254
369
|
]);
|
|
370
|
+
|
|
371
|
+
// Get task by ID
|
|
372
|
+
const task = await tem.task.getById(taskId);
|
|
255
373
|
```
|
|
256
374
|
|
|
257
375
|
### Worker
|
|
258
376
|
|
|
259
377
|
```typescript
|
|
260
378
|
// Register handler
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const result = await doWork(
|
|
264
|
-
return result; //
|
|
379
|
+
// payload: your task data; context: { taskId, batchId, attempt }
|
|
380
|
+
tem.worker.register("task-type", async (payload, context) => {
|
|
381
|
+
const result = await doWork(payload);
|
|
382
|
+
return result; // JSON-serialized to task.result
|
|
265
383
|
});
|
|
266
384
|
|
|
267
385
|
// Control execution
|
|
268
386
|
tem.worker.start();
|
|
269
|
-
await tem.
|
|
387
|
+
await tem.stop(); // Stops worker and closes DB
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### NonRetryableError
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { NonRetryableError } from "@qianxude/tem";
|
|
394
|
+
|
|
395
|
+
throw new NonRetryableError("reason");
|
|
396
|
+
// Task goes to 'failed' immediately, skipping remaining attempts
|
|
270
397
|
```
|
|
271
398
|
|
|
272
399
|
---
|
|
@@ -298,6 +425,37 @@ await tem.worker.stop();
|
|
|
298
425
|
|
|
299
426
|
---
|
|
300
427
|
|
|
428
|
+
## Mock Server
|
|
429
|
+
|
|
430
|
+
Tem includes a built-in mock HTTP server for testing task execution under various constraints. Use it to simulate APIs with:
|
|
431
|
+
|
|
432
|
+
- **Concurrency limits** — Test how your tasks handle 503 errors
|
|
433
|
+
- **Rate limiting** — Verify retry behavior against 429 responses
|
|
434
|
+
- **Error simulation** — Test resilience with configurable failure rates
|
|
435
|
+
|
|
436
|
+
See [src/mock-server/README.md](src/mock-server/README.md) for detailed documentation.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## CLI
|
|
441
|
+
|
|
442
|
+
Tem includes a CLI for batch diagnostics and monitoring:
|
|
443
|
+
|
|
444
|
+
```sh
|
|
445
|
+
# Generate diagnostic report
|
|
446
|
+
tem report ./tem.db my-batch
|
|
447
|
+
|
|
448
|
+
# List failed tasks
|
|
449
|
+
tem list ./tem.db --batch my-batch --status failed
|
|
450
|
+
|
|
451
|
+
# Watch batch progress in real-time
|
|
452
|
+
tem watch ./tem.db --latest
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
See [src/cli/README.md](src/cli/README.md) for full documentation.
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
301
459
|
## License
|
|
302
460
|
|
|
303
461
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qianxude/tem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "A lightweight task execution engine for IO-bound workloads with SQLite persistence, retry, and rate limiting",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"test": "bun test",
|
|
25
|
+
"coverage": "bun test --coverage",
|
|
25
26
|
"test:integration": "bun test tests/integration/*.test.ts",
|
|
26
27
|
"test:mock-server": "bun test tests/integration/mock-server.test.ts",
|
|
27
28
|
"test:simple-tasks": "bun test tests/integration/tem-with-mock-server.test.ts",
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
"test:auto-detect": "bun test tests/integration/auto-detect.test.ts",
|
|
30
31
|
"lint": "oxlint",
|
|
31
32
|
"lint:file": "oxlint",
|
|
33
|
+
"example:llm-detect": "bun examples/llm-detect.ts",
|
|
32
34
|
"dev": "bun --watch src/index.ts",
|
|
33
35
|
"cli": "bun ./src/cli/index.ts",
|
|
34
36
|
"publish:pkg": "bun publish --access public",
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# tem CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for batch diagnostics and monitoring.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The CLI is included with the tem package:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
bun add @qianxude/tem
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
You can run it directly with bun:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bun run src/cli/index.ts <command> [options]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install globally:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bun link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
tem <command> [options]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
### `report`
|
|
34
|
+
|
|
35
|
+
Generate a diagnostic report for batches.
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
tem report <db-path> [batch-code]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Arguments:**
|
|
42
|
+
|
|
43
|
+
- `db-path` - Path to the SQLite database file (required)
|
|
44
|
+
- `batch-code` - Specific batch code to report on (optional)
|
|
45
|
+
|
|
46
|
+
**Options:**
|
|
47
|
+
|
|
48
|
+
- `--latest` - Report on the most recently created batch
|
|
49
|
+
- `--limit-errors N` - Show top N error patterns (default: 10)
|
|
50
|
+
|
|
51
|
+
**Examples:**
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
# Summary report for all batches
|
|
55
|
+
tem report ./tem.db
|
|
56
|
+
|
|
57
|
+
# Detailed report for specific batch
|
|
58
|
+
tem report ./tem.db my-batch-code
|
|
59
|
+
|
|
60
|
+
# Report on latest batch
|
|
61
|
+
tem report ./tem.db --latest
|
|
62
|
+
|
|
63
|
+
# Show top 20 error patterns
|
|
64
|
+
tem report ./tem.db my-batch-code --limit-errors 20
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Report includes:**
|
|
68
|
+
|
|
69
|
+
- Batch overview (code, type, status, timestamps, duration)
|
|
70
|
+
- Status breakdown with counts and percentages
|
|
71
|
+
- Timing analysis (avg/min/max task times, throughput)
|
|
72
|
+
- Error patterns for failed tasks
|
|
73
|
+
- Retry analysis statistics
|
|
74
|
+
- Detection of stuck tasks (running > 5 minutes)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `list`
|
|
79
|
+
|
|
80
|
+
List tasks with filtering options.
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
tem list <db-path>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Arguments:**
|
|
87
|
+
|
|
88
|
+
- `db-path` - Path to the SQLite database file (required)
|
|
89
|
+
|
|
90
|
+
**Options:**
|
|
91
|
+
|
|
92
|
+
- `--batch <code>` - Filter by batch code
|
|
93
|
+
- `--status <status>` - Filter by status: `pending`, `running`, `completed`, or `failed`
|
|
94
|
+
- `--type <type>` - Filter by task type
|
|
95
|
+
- `--limit <n>` - Limit results (default: 100)
|
|
96
|
+
|
|
97
|
+
**Examples:**
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
# List all tasks (up to 100)
|
|
101
|
+
tem list ./tem.db
|
|
102
|
+
|
|
103
|
+
# List failed tasks from a specific batch
|
|
104
|
+
tem list ./tem.db --batch my-batch --status failed
|
|
105
|
+
|
|
106
|
+
# List pending tasks of a specific type
|
|
107
|
+
tem list ./tem.db --status pending --type rewrite --limit 20
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Output columns:**
|
|
111
|
+
|
|
112
|
+
- ID - Task UUID
|
|
113
|
+
- Batch - Batch code
|
|
114
|
+
- Type - Task type
|
|
115
|
+
- Status - Current status
|
|
116
|
+
- Attempts - Current attempt / max attempts
|
|
117
|
+
- Created - Timestamp
|
|
118
|
+
- Completed - Completion timestamp
|
|
119
|
+
- Error - Truncated error message (if failed)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `watch`
|
|
124
|
+
|
|
125
|
+
Monitor a running batch in real-time.
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
tem watch <db-path> [batch-code]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Arguments:**
|
|
132
|
+
|
|
133
|
+
- `db-path` - Path to the SQLite database file (required)
|
|
134
|
+
- `batch-code` - Specific batch code to watch (optional if using `--latest`)
|
|
135
|
+
|
|
136
|
+
**Options:**
|
|
137
|
+
|
|
138
|
+
- `--latest` - Watch the most recently created batch
|
|
139
|
+
- `--interval N` - Refresh interval in seconds (default: 5)
|
|
140
|
+
- `--timeout N` - Maximum watch time in seconds (default: 3600)
|
|
141
|
+
- `--no-clear` - Don't clear screen between updates
|
|
142
|
+
|
|
143
|
+
**Examples:**
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
# Watch the latest batch
|
|
147
|
+
tem watch ./tem.db --latest
|
|
148
|
+
|
|
149
|
+
# Watch specific batch with 10-second refresh
|
|
150
|
+
tem watch ./tem.db my-batch-code --interval 10
|
|
151
|
+
|
|
152
|
+
# Watch for up to 5 minutes
|
|
153
|
+
tem watch ./tem.db --latest --timeout 300
|
|
154
|
+
|
|
155
|
+
# Watch without clearing screen (for logging)
|
|
156
|
+
tem watch ./tem.db --latest --no-clear
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Watch display includes:**
|
|
160
|
+
|
|
161
|
+
- Visual progress bar
|
|
162
|
+
- Batch status with color coding:
|
|
163
|
+
- 🟢 Green - Completed
|
|
164
|
+
- 🔴 Red - Failed
|
|
165
|
+
- 🟡 Yellow - Running
|
|
166
|
+
- 🔵 Cyan - Pending
|
|
167
|
+
- Real-time statistics (pending, running, completed, failed, total)
|
|
168
|
+
- Throughput and ETA
|
|
169
|
+
- Recent errors (last 3)
|
|
170
|
+
- Stuck task warnings (> 5 minutes running)
|
|
171
|
+
|
|
172
|
+
Press `Ctrl+C` to stop watching. A final report is displayed when the batch completes.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Exit Codes
|
|
177
|
+
|
|
178
|
+
| Code | Meaning |
|
|
179
|
+
|------|---------|
|
|
180
|
+
| 0 | Success |
|
|
181
|
+
| 1 | Runtime error (database issues, batch not found, timeout) |
|
|
182
|
+
| 2 | Usage error (missing arguments, invalid commands/options) |
|
|
183
|
+
| 130 | Interrupted by user (SIGINT) |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Global Options
|
|
188
|
+
|
|
189
|
+
- `--help, -h` - Show help message for any command
|
|
190
|
+
|
|
191
|
+
## Common Workflows
|
|
192
|
+
|
|
193
|
+
### Debug a failing batch
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
# Watch the batch in one terminal
|
|
197
|
+
tem watch ./tem.db my-batch --latest
|
|
198
|
+
|
|
199
|
+
# In another terminal, list failed tasks
|
|
200
|
+
tem list ./tem.db --batch my-batch --status failed
|
|
201
|
+
|
|
202
|
+
# Generate detailed report
|
|
203
|
+
tem report ./tem.db my-batch --limit-errors 20
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Monitor a long-running job
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
# Watch with longer interval to reduce database queries
|
|
210
|
+
tem watch ./tem.db my-batch --interval 30 --timeout 7200
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Quick status check
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
# Summary of all batches
|
|
217
|
+
tem report ./tem.db
|
|
218
|
+
```
|
package/src/core/tem.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database, type DatabaseOptions } from '../database/index.js';
|
|
2
|
-
import { BatchService, TaskService } from '../services/index.js';
|
|
2
|
+
import { BatchService, TaskService, BatchInterruptionService } from '../services/index.js';
|
|
3
3
|
import { Worker, type WorkerConfig } from './worker.js';
|
|
4
4
|
import {
|
|
5
5
|
detectConstraints,
|
|
@@ -27,12 +27,16 @@ export interface TEMConfig {
|
|
|
27
27
|
|
|
28
28
|
// Polling
|
|
29
29
|
pollIntervalMs: number;
|
|
30
|
+
|
|
31
|
+
// Optional: Specific batch ID to process (if set, only processes this batch)
|
|
32
|
+
batchId?: string;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export class TEM {
|
|
33
36
|
readonly batch: BatchService;
|
|
34
37
|
readonly task: TaskService;
|
|
35
38
|
readonly worker: Worker;
|
|
39
|
+
readonly interruption: BatchInterruptionService;
|
|
36
40
|
|
|
37
41
|
private database: Database;
|
|
38
42
|
|
|
@@ -70,6 +74,7 @@ export class TEM {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
constructor(config: TEMConfig) {
|
|
77
|
+
|
|
73
78
|
// Initialize database
|
|
74
79
|
const dbOptions: DatabaseOptions = {
|
|
75
80
|
path: config.databasePath,
|
|
@@ -79,12 +84,15 @@ export class TEM {
|
|
|
79
84
|
// Initialize services
|
|
80
85
|
this.batch = new BatchService(this.database);
|
|
81
86
|
this.task = new TaskService(this.database);
|
|
87
|
+
this.interruption = new BatchInterruptionService(this.database, this.batch);
|
|
82
88
|
|
|
83
89
|
// Initialize worker with config
|
|
84
90
|
const workerConfig: WorkerConfig = {
|
|
85
91
|
concurrency: config.concurrency,
|
|
86
92
|
pollIntervalMs: config.pollIntervalMs,
|
|
87
93
|
rateLimit: config.rateLimit,
|
|
94
|
+
batchId: config.batchId,
|
|
95
|
+
interruptionService: this.interruption,
|
|
88
96
|
};
|
|
89
97
|
this.worker = new Worker(this.task, workerConfig);
|
|
90
98
|
}
|
|
@@ -97,4 +105,24 @@ export class TEM {
|
|
|
97
105
|
await this.worker.stop();
|
|
98
106
|
this.database.close();
|
|
99
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Manually interrupt a batch with a specified reason.
|
|
111
|
+
* This will stop the worker if processing this batch and prevent further tasks from being claimed.
|
|
112
|
+
*
|
|
113
|
+
* @param batchId - The ID of the batch to interrupt
|
|
114
|
+
* @param reason - The reason for interruption (default: 'manual')
|
|
115
|
+
* @param message - Optional custom message explaining the interruption
|
|
116
|
+
*/
|
|
117
|
+
async interruptBatch(
|
|
118
|
+
batchId: string,
|
|
119
|
+
reason?: import('../interfaces/index.js').BatchInterruptionReason,
|
|
120
|
+
message?: string
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
await this.interruption.interrupt(
|
|
123
|
+
batchId,
|
|
124
|
+
reason ?? 'manual',
|
|
125
|
+
message ?? 'Batch manually interrupted'
|
|
126
|
+
);
|
|
127
|
+
}
|
|
100
128
|
}
|