@guanghechen/task 1.0.0-alpha.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/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # 1.0.0-alpha.1 (2023-08-27)
7
+
8
+
9
+ ### Features
10
+
11
+ * ✨ add @guanghechen/task ([83b66a5](https://github.com/guanghechen/sora/commit/83b66a52d84dd43e4125ca40bc297a511f5712f1))
12
+
13
+
14
+ ### Performance Improvements
15
+
16
+ * 🔧 update path alias ([c396ec3](https://github.com/guanghechen/sora/commit/c396ec3316b2b19a69ba0234cc7a9d86edd9fac2))
17
+ * ⬆️ upgrade devDependencies ([2b70a3f](https://github.com/guanghechen/sora/commit/2b70a3f5b895ed51de035b962d843661475663d6))
@@ -0,0 +1,301 @@
1
+ 'use strict';
2
+
3
+ var monitor = require('@guanghechen/monitor');
4
+
5
+ exports.TaskStatus = void 0;
6
+ (function (TaskStatus) {
7
+ TaskStatus[TaskStatus["PENDING"] = 1] = "PENDING";
8
+ TaskStatus[TaskStatus["RUNNING"] = 2] = "RUNNING";
9
+ TaskStatus[TaskStatus["SUSPENDED"] = 4] = "SUSPENDED";
10
+ TaskStatus[TaskStatus["CANCELLED"] = 8] = "CANCELLED";
11
+ TaskStatus[TaskStatus["FAILED"] = 16] = "FAILED";
12
+ TaskStatus[TaskStatus["FINISHED"] = 32] = "FINISHED";
13
+ TaskStatus[TaskStatus["ATTEMPT_SUSPENDING"] = 64] = "ATTEMPT_SUSPENDING";
14
+ TaskStatus[TaskStatus["ATTEMPT_RESUMING"] = 128] = "ATTEMPT_RESUMING";
15
+ TaskStatus[TaskStatus["ATTEMPT_CANCELING"] = 256] = "ATTEMPT_CANCELING";
16
+ TaskStatus[TaskStatus["ATTEMPT_FINISHING"] = 512] = "ATTEMPT_FINISHING";
17
+ })(exports.TaskStatus || (exports.TaskStatus = {}));
18
+ exports.TaskStrategy = void 0;
19
+ (function (TaskStrategy) {
20
+ TaskStrategy[TaskStrategy["ABORT_ON_ERROR"] = 1] = "ABORT_ON_ERROR";
21
+ TaskStrategy[TaskStrategy["CONTINUE_ON_ERROR"] = 2] = "CONTINUE_ON_ERROR";
22
+ })(exports.TaskStrategy || (exports.TaskStrategy = {}));
23
+ const active = exports.TaskStatus.RUNNING |
24
+ exports.TaskStatus.ATTEMPT_SUSPENDING |
25
+ exports.TaskStatus.ATTEMPT_RESUMING;
26
+ const alive = exports.TaskStatus.PENDING |
27
+ exports.TaskStatus.RUNNING |
28
+ exports.TaskStatus.SUSPENDED |
29
+ exports.TaskStatus.ATTEMPT_SUSPENDING |
30
+ exports.TaskStatus.ATTEMPT_RESUMING;
31
+ const terminated = exports.TaskStatus.CANCELLED |
32
+ exports.TaskStatus.FAILED |
33
+ exports.TaskStatus.FINISHED;
34
+
35
+ const noop = () => { };
36
+ class TaskState {
37
+ name;
38
+ _monitors;
39
+ _errorDetails;
40
+ _status;
41
+ constructor(name) {
42
+ this.name = name;
43
+ this._monitors = {
44
+ onAddError: new monitor.Monitor('onAddError'),
45
+ onStatusChange: new monitor.Monitor('onStatusChange'),
46
+ };
47
+ this._errorDetails = [];
48
+ this._status = exports.TaskStatus.PENDING;
49
+ }
50
+ get status() {
51
+ return this._status;
52
+ }
53
+ get active() {
54
+ return (this._status & active) > 0;
55
+ }
56
+ get alive() {
57
+ return (this._status & alive) > 0;
58
+ }
59
+ get terminated() {
60
+ return (this._status & terminated) > 0;
61
+ }
62
+ get hasError() {
63
+ return this._errorDetails.length > 0;
64
+ }
65
+ get error() {
66
+ if (this._errorDetails.length === 0)
67
+ return undefined;
68
+ return { from: this.name, details: this._errorDetails.slice() };
69
+ }
70
+ set status(status) {
71
+ const curStatus = this._status;
72
+ if (status !== curStatus) {
73
+ const accepted = this.check(status);
74
+ if (accepted) {
75
+ this._status = status;
76
+ this._monitors.onStatusChange.notify(status, curStatus);
77
+ }
78
+ else {
79
+ throw new TypeError(`[transit] unexpected status: task(${this.name}) cur(${curStatus}) next(${status})`);
80
+ }
81
+ }
82
+ }
83
+ monitor(monitor) {
84
+ if (this.terminated)
85
+ return noop;
86
+ const { onAddError, onStatusChange } = monitor;
87
+ const unsubscribeOnAddError = onAddError
88
+ ? this._monitors.onAddError.subscribe(onAddError)
89
+ : noop;
90
+ const unsubscribeOnStatusChange = onStatusChange
91
+ ? this._monitors.onStatusChange.subscribe(onStatusChange)
92
+ : noop;
93
+ return () => {
94
+ unsubscribeOnAddError();
95
+ unsubscribeOnStatusChange();
96
+ };
97
+ }
98
+ check(nextStatus) {
99
+ const status = this._status;
100
+ switch (nextStatus) {
101
+ case exports.TaskStatus.PENDING:
102
+ return false;
103
+ case exports.TaskStatus.RUNNING:
104
+ return status === exports.TaskStatus.PENDING || status === exports.TaskStatus.ATTEMPT_RESUMING;
105
+ case exports.TaskStatus.SUSPENDED:
106
+ return status === exports.TaskStatus.ATTEMPT_SUSPENDING;
107
+ case exports.TaskStatus.CANCELLED:
108
+ return status === exports.TaskStatus.ATTEMPT_CANCELING;
109
+ case exports.TaskStatus.FAILED:
110
+ return (status !== exports.TaskStatus.PENDING &&
111
+ status !== exports.TaskStatus.SUSPENDED &&
112
+ (status & terminated) === 0);
113
+ case exports.TaskStatus.FINISHED:
114
+ return (status !== exports.TaskStatus.PENDING &&
115
+ status !== exports.TaskStatus.SUSPENDED &&
116
+ (status & terminated) === 0);
117
+ case exports.TaskStatus.ATTEMPT_SUSPENDING:
118
+ return status === exports.TaskStatus.RUNNING;
119
+ case exports.TaskStatus.ATTEMPT_RESUMING:
120
+ return status === exports.TaskStatus.SUSPENDED;
121
+ case exports.TaskStatus.ATTEMPT_CANCELING:
122
+ return (status & alive) > 0;
123
+ case exports.TaskStatus.ATTEMPT_FINISHING:
124
+ return status !== exports.TaskStatus.PENDING && (status & alive) > 0;
125
+ default:
126
+ return false;
127
+ }
128
+ }
129
+ cleanup() {
130
+ if (!this.terminated)
131
+ throw new Error(`[cleanup] task(${this.name}) is not terminated`);
132
+ this._errorDetails.length = 0;
133
+ this._monitors.onStatusChange.destroy();
134
+ this._monitors.onAddError.destroy();
135
+ }
136
+ _addError(type, error) {
137
+ this._errorDetails.push({ type, error });
138
+ this._monitors.onAddError.notify(type, error);
139
+ }
140
+ }
141
+
142
+ class AtomicTask extends TaskState {
143
+ _promise;
144
+ constructor(name) {
145
+ super(name);
146
+ this._promise = undefined;
147
+ }
148
+ async start() {
149
+ if (this.status === exports.TaskStatus.PENDING) {
150
+ this.status = exports.TaskStatus.RUNNING;
151
+ this._promise = this.run()
152
+ .then(() => {
153
+ this.status = exports.TaskStatus.FINISHED;
154
+ })
155
+ .catch(error => {
156
+ this.status = exports.TaskStatus.FAILED;
157
+ this._addError('AtomicTaskError', error);
158
+ });
159
+ }
160
+ return this._promise;
161
+ }
162
+ async pause() {
163
+ await this._promise;
164
+ }
165
+ async resume() {
166
+ await this._promise;
167
+ }
168
+ async cancel() {
169
+ if (this.status === exports.TaskStatus.PENDING) {
170
+ this.status = exports.TaskStatus.ATTEMPT_CANCELING;
171
+ this.status = exports.TaskStatus.CANCELLED;
172
+ return;
173
+ }
174
+ if (this.alive)
175
+ this.status = exports.TaskStatus.ATTEMPT_CANCELING;
176
+ await this._promise;
177
+ }
178
+ async finish() {
179
+ if (this.status === exports.TaskStatus.PENDING)
180
+ await this.start();
181
+ if (this.alive)
182
+ this.status = exports.TaskStatus.ATTEMPT_FINISHING;
183
+ await this._promise;
184
+ }
185
+ }
186
+
187
+ class ResumableTask extends TaskState {
188
+ strategy;
189
+ _pollInterval;
190
+ _execution;
191
+ _step;
192
+ constructor(props) {
193
+ super(props.name);
194
+ this.strategy = props.strategy;
195
+ this._pollInterval = Math.max(0, props.pollInterval);
196
+ this._execution = undefined;
197
+ this._step = undefined;
198
+ }
199
+ async start() {
200
+ if (this.status === exports.TaskStatus.PENDING) {
201
+ this.status = exports.TaskStatus.RUNNING;
202
+ this._execution = this.run();
203
+ this.launchStep();
204
+ await this._step;
205
+ }
206
+ }
207
+ async pause() {
208
+ if (this.status === exports.TaskStatus.RUNNING) {
209
+ this.status = exports.TaskStatus.ATTEMPT_SUSPENDING;
210
+ await this._step;
211
+ if (this.status === exports.TaskStatus.ATTEMPT_SUSPENDING) {
212
+ this.status = exports.TaskStatus.SUSPENDED;
213
+ }
214
+ }
215
+ }
216
+ async resume() {
217
+ if (this.status === exports.TaskStatus.SUSPENDED) {
218
+ this.status = exports.TaskStatus.ATTEMPT_RESUMING;
219
+ await this._step;
220
+ if (this.status === exports.TaskStatus.ATTEMPT_RESUMING) {
221
+ this.status = exports.TaskStatus.RUNNING;
222
+ this.queueStep();
223
+ }
224
+ }
225
+ }
226
+ async cancel() {
227
+ if (this.alive) {
228
+ this.status = exports.TaskStatus.ATTEMPT_CANCELING;
229
+ await this._step;
230
+ if (this.status === exports.TaskStatus.ATTEMPT_CANCELING) {
231
+ this.status = exports.TaskStatus.CANCELLED;
232
+ }
233
+ }
234
+ }
235
+ async finish() {
236
+ if (this.status === exports.TaskStatus.PENDING)
237
+ await this.start();
238
+ if (this.alive) {
239
+ this.status = exports.TaskStatus.ATTEMPT_FINISHING;
240
+ await this._step;
241
+ const execution = this._execution;
242
+ if (execution) {
243
+ while (this.status === exports.TaskStatus.ATTEMPT_FINISHING) {
244
+ const step = execution.next();
245
+ if (step.done) {
246
+ this.status = this.hasError ? exports.TaskStatus.FAILED : exports.TaskStatus.FINISHED;
247
+ break;
248
+ }
249
+ await step.value.catch(error => {
250
+ this._addError('ResumableTaskError', error);
251
+ switch (this.strategy) {
252
+ case exports.TaskStrategy.ABORT_ON_ERROR:
253
+ if (!this.terminated)
254
+ this.status = exports.TaskStatus.FAILED;
255
+ break;
256
+ case exports.TaskStrategy.CONTINUE_ON_ERROR:
257
+ break;
258
+ }
259
+ });
260
+ }
261
+ }
262
+ }
263
+ }
264
+ launchStep() {
265
+ if (this.status === exports.TaskStatus.RUNNING && this._step === undefined && this._execution) {
266
+ const step = this._execution.next();
267
+ if (step.done) {
268
+ this.status = this.hasError ? exports.TaskStatus.FAILED : exports.TaskStatus.FINISHED;
269
+ return;
270
+ }
271
+ this._step = step.value
272
+ .then(() => {
273
+ this._step = undefined;
274
+ this.queueStep();
275
+ })
276
+ .catch(error => {
277
+ this._step = undefined;
278
+ this._addError('ResumableTaskError', error);
279
+ switch (this.strategy) {
280
+ case exports.TaskStrategy.ABORT_ON_ERROR:
281
+ if (!this.terminated)
282
+ this.status = exports.TaskStatus.FAILED;
283
+ break;
284
+ case exports.TaskStrategy.CONTINUE_ON_ERROR:
285
+ this.queueStep();
286
+ break;
287
+ }
288
+ });
289
+ }
290
+ }
291
+ queueStep() {
292
+ setTimeout(() => this.launchStep(), this._pollInterval);
293
+ }
294
+ }
295
+
296
+ exports.AtomicTask = AtomicTask;
297
+ exports.ResumableTask = ResumableTask;
298
+ exports.TaskState = TaskState;
299
+ exports.active = active;
300
+ exports.alive = alive;
301
+ exports.terminated = terminated;
@@ -0,0 +1,294 @@
1
+ import { Monitor } from '@guanghechen/monitor';
2
+
3
+ var TaskStatus;
4
+ (function (TaskStatus) {
5
+ TaskStatus[TaskStatus["PENDING"] = 1] = "PENDING";
6
+ TaskStatus[TaskStatus["RUNNING"] = 2] = "RUNNING";
7
+ TaskStatus[TaskStatus["SUSPENDED"] = 4] = "SUSPENDED";
8
+ TaskStatus[TaskStatus["CANCELLED"] = 8] = "CANCELLED";
9
+ TaskStatus[TaskStatus["FAILED"] = 16] = "FAILED";
10
+ TaskStatus[TaskStatus["FINISHED"] = 32] = "FINISHED";
11
+ TaskStatus[TaskStatus["ATTEMPT_SUSPENDING"] = 64] = "ATTEMPT_SUSPENDING";
12
+ TaskStatus[TaskStatus["ATTEMPT_RESUMING"] = 128] = "ATTEMPT_RESUMING";
13
+ TaskStatus[TaskStatus["ATTEMPT_CANCELING"] = 256] = "ATTEMPT_CANCELING";
14
+ TaskStatus[TaskStatus["ATTEMPT_FINISHING"] = 512] = "ATTEMPT_FINISHING";
15
+ })(TaskStatus || (TaskStatus = {}));
16
+ var TaskStrategy;
17
+ (function (TaskStrategy) {
18
+ TaskStrategy[TaskStrategy["ABORT_ON_ERROR"] = 1] = "ABORT_ON_ERROR";
19
+ TaskStrategy[TaskStrategy["CONTINUE_ON_ERROR"] = 2] = "CONTINUE_ON_ERROR";
20
+ })(TaskStrategy || (TaskStrategy = {}));
21
+ const active = TaskStatus.RUNNING |
22
+ TaskStatus.ATTEMPT_SUSPENDING |
23
+ TaskStatus.ATTEMPT_RESUMING;
24
+ const alive = TaskStatus.PENDING |
25
+ TaskStatus.RUNNING |
26
+ TaskStatus.SUSPENDED |
27
+ TaskStatus.ATTEMPT_SUSPENDING |
28
+ TaskStatus.ATTEMPT_RESUMING;
29
+ const terminated = TaskStatus.CANCELLED |
30
+ TaskStatus.FAILED |
31
+ TaskStatus.FINISHED;
32
+
33
+ const noop = () => { };
34
+ class TaskState {
35
+ name;
36
+ _monitors;
37
+ _errorDetails;
38
+ _status;
39
+ constructor(name) {
40
+ this.name = name;
41
+ this._monitors = {
42
+ onAddError: new Monitor('onAddError'),
43
+ onStatusChange: new Monitor('onStatusChange'),
44
+ };
45
+ this._errorDetails = [];
46
+ this._status = TaskStatus.PENDING;
47
+ }
48
+ get status() {
49
+ return this._status;
50
+ }
51
+ get active() {
52
+ return (this._status & active) > 0;
53
+ }
54
+ get alive() {
55
+ return (this._status & alive) > 0;
56
+ }
57
+ get terminated() {
58
+ return (this._status & terminated) > 0;
59
+ }
60
+ get hasError() {
61
+ return this._errorDetails.length > 0;
62
+ }
63
+ get error() {
64
+ if (this._errorDetails.length === 0)
65
+ return undefined;
66
+ return { from: this.name, details: this._errorDetails.slice() };
67
+ }
68
+ set status(status) {
69
+ const curStatus = this._status;
70
+ if (status !== curStatus) {
71
+ const accepted = this.check(status);
72
+ if (accepted) {
73
+ this._status = status;
74
+ this._monitors.onStatusChange.notify(status, curStatus);
75
+ }
76
+ else {
77
+ throw new TypeError(`[transit] unexpected status: task(${this.name}) cur(${curStatus}) next(${status})`);
78
+ }
79
+ }
80
+ }
81
+ monitor(monitor) {
82
+ if (this.terminated)
83
+ return noop;
84
+ const { onAddError, onStatusChange } = monitor;
85
+ const unsubscribeOnAddError = onAddError
86
+ ? this._monitors.onAddError.subscribe(onAddError)
87
+ : noop;
88
+ const unsubscribeOnStatusChange = onStatusChange
89
+ ? this._monitors.onStatusChange.subscribe(onStatusChange)
90
+ : noop;
91
+ return () => {
92
+ unsubscribeOnAddError();
93
+ unsubscribeOnStatusChange();
94
+ };
95
+ }
96
+ check(nextStatus) {
97
+ const status = this._status;
98
+ switch (nextStatus) {
99
+ case TaskStatus.PENDING:
100
+ return false;
101
+ case TaskStatus.RUNNING:
102
+ return status === TaskStatus.PENDING || status === TaskStatus.ATTEMPT_RESUMING;
103
+ case TaskStatus.SUSPENDED:
104
+ return status === TaskStatus.ATTEMPT_SUSPENDING;
105
+ case TaskStatus.CANCELLED:
106
+ return status === TaskStatus.ATTEMPT_CANCELING;
107
+ case TaskStatus.FAILED:
108
+ return (status !== TaskStatus.PENDING &&
109
+ status !== TaskStatus.SUSPENDED &&
110
+ (status & terminated) === 0);
111
+ case TaskStatus.FINISHED:
112
+ return (status !== TaskStatus.PENDING &&
113
+ status !== TaskStatus.SUSPENDED &&
114
+ (status & terminated) === 0);
115
+ case TaskStatus.ATTEMPT_SUSPENDING:
116
+ return status === TaskStatus.RUNNING;
117
+ case TaskStatus.ATTEMPT_RESUMING:
118
+ return status === TaskStatus.SUSPENDED;
119
+ case TaskStatus.ATTEMPT_CANCELING:
120
+ return (status & alive) > 0;
121
+ case TaskStatus.ATTEMPT_FINISHING:
122
+ return status !== TaskStatus.PENDING && (status & alive) > 0;
123
+ default:
124
+ return false;
125
+ }
126
+ }
127
+ cleanup() {
128
+ if (!this.terminated)
129
+ throw new Error(`[cleanup] task(${this.name}) is not terminated`);
130
+ this._errorDetails.length = 0;
131
+ this._monitors.onStatusChange.destroy();
132
+ this._monitors.onAddError.destroy();
133
+ }
134
+ _addError(type, error) {
135
+ this._errorDetails.push({ type, error });
136
+ this._monitors.onAddError.notify(type, error);
137
+ }
138
+ }
139
+
140
+ class AtomicTask extends TaskState {
141
+ _promise;
142
+ constructor(name) {
143
+ super(name);
144
+ this._promise = undefined;
145
+ }
146
+ async start() {
147
+ if (this.status === TaskStatus.PENDING) {
148
+ this.status = TaskStatus.RUNNING;
149
+ this._promise = this.run()
150
+ .then(() => {
151
+ this.status = TaskStatus.FINISHED;
152
+ })
153
+ .catch(error => {
154
+ this.status = TaskStatus.FAILED;
155
+ this._addError('AtomicTaskError', error);
156
+ });
157
+ }
158
+ return this._promise;
159
+ }
160
+ async pause() {
161
+ await this._promise;
162
+ }
163
+ async resume() {
164
+ await this._promise;
165
+ }
166
+ async cancel() {
167
+ if (this.status === TaskStatus.PENDING) {
168
+ this.status = TaskStatus.ATTEMPT_CANCELING;
169
+ this.status = TaskStatus.CANCELLED;
170
+ return;
171
+ }
172
+ if (this.alive)
173
+ this.status = TaskStatus.ATTEMPT_CANCELING;
174
+ await this._promise;
175
+ }
176
+ async finish() {
177
+ if (this.status === TaskStatus.PENDING)
178
+ await this.start();
179
+ if (this.alive)
180
+ this.status = TaskStatus.ATTEMPT_FINISHING;
181
+ await this._promise;
182
+ }
183
+ }
184
+
185
+ class ResumableTask extends TaskState {
186
+ strategy;
187
+ _pollInterval;
188
+ _execution;
189
+ _step;
190
+ constructor(props) {
191
+ super(props.name);
192
+ this.strategy = props.strategy;
193
+ this._pollInterval = Math.max(0, props.pollInterval);
194
+ this._execution = undefined;
195
+ this._step = undefined;
196
+ }
197
+ async start() {
198
+ if (this.status === TaskStatus.PENDING) {
199
+ this.status = TaskStatus.RUNNING;
200
+ this._execution = this.run();
201
+ this.launchStep();
202
+ await this._step;
203
+ }
204
+ }
205
+ async pause() {
206
+ if (this.status === TaskStatus.RUNNING) {
207
+ this.status = TaskStatus.ATTEMPT_SUSPENDING;
208
+ await this._step;
209
+ if (this.status === TaskStatus.ATTEMPT_SUSPENDING) {
210
+ this.status = TaskStatus.SUSPENDED;
211
+ }
212
+ }
213
+ }
214
+ async resume() {
215
+ if (this.status === TaskStatus.SUSPENDED) {
216
+ this.status = TaskStatus.ATTEMPT_RESUMING;
217
+ await this._step;
218
+ if (this.status === TaskStatus.ATTEMPT_RESUMING) {
219
+ this.status = TaskStatus.RUNNING;
220
+ this.queueStep();
221
+ }
222
+ }
223
+ }
224
+ async cancel() {
225
+ if (this.alive) {
226
+ this.status = TaskStatus.ATTEMPT_CANCELING;
227
+ await this._step;
228
+ if (this.status === TaskStatus.ATTEMPT_CANCELING) {
229
+ this.status = TaskStatus.CANCELLED;
230
+ }
231
+ }
232
+ }
233
+ async finish() {
234
+ if (this.status === TaskStatus.PENDING)
235
+ await this.start();
236
+ if (this.alive) {
237
+ this.status = TaskStatus.ATTEMPT_FINISHING;
238
+ await this._step;
239
+ const execution = this._execution;
240
+ if (execution) {
241
+ while (this.status === TaskStatus.ATTEMPT_FINISHING) {
242
+ const step = execution.next();
243
+ if (step.done) {
244
+ this.status = this.hasError ? TaskStatus.FAILED : TaskStatus.FINISHED;
245
+ break;
246
+ }
247
+ await step.value.catch(error => {
248
+ this._addError('ResumableTaskError', error);
249
+ switch (this.strategy) {
250
+ case TaskStrategy.ABORT_ON_ERROR:
251
+ if (!this.terminated)
252
+ this.status = TaskStatus.FAILED;
253
+ break;
254
+ case TaskStrategy.CONTINUE_ON_ERROR:
255
+ break;
256
+ }
257
+ });
258
+ }
259
+ }
260
+ }
261
+ }
262
+ launchStep() {
263
+ if (this.status === TaskStatus.RUNNING && this._step === undefined && this._execution) {
264
+ const step = this._execution.next();
265
+ if (step.done) {
266
+ this.status = this.hasError ? TaskStatus.FAILED : TaskStatus.FINISHED;
267
+ return;
268
+ }
269
+ this._step = step.value
270
+ .then(() => {
271
+ this._step = undefined;
272
+ this.queueStep();
273
+ })
274
+ .catch(error => {
275
+ this._step = undefined;
276
+ this._addError('ResumableTaskError', error);
277
+ switch (this.strategy) {
278
+ case TaskStrategy.ABORT_ON_ERROR:
279
+ if (!this.terminated)
280
+ this.status = TaskStatus.FAILED;
281
+ break;
282
+ case TaskStrategy.CONTINUE_ON_ERROR:
283
+ this.queueStep();
284
+ break;
285
+ }
286
+ });
287
+ }
288
+ }
289
+ queueStep() {
290
+ setTimeout(() => this.launchStep(), this._pollInterval);
291
+ }
292
+ }
293
+
294
+ export { AtomicTask, ResumableTask, TaskState, TaskStatus, TaskStrategy, active, alive, terminated };
@@ -0,0 +1,136 @@
1
+ declare enum TaskStatus {
2
+ PENDING = 1,
3
+ RUNNING = 2,
4
+ SUSPENDED = 4,
5
+ CANCELLED = 8,
6
+ FAILED = 16,
7
+ FINISHED = 32,
8
+ ATTEMPT_SUSPENDING = 64,
9
+ ATTEMPT_RESUMING = 128,
10
+ ATTEMPT_CANCELING = 256,
11
+ ATTEMPT_FINISHING = 512
12
+ }
13
+ declare enum TaskStrategy {
14
+ ABORT_ON_ERROR = 1,
15
+ CONTINUE_ON_ERROR = 2
16
+ }
17
+ declare const active: number;
18
+ declare const alive: number;
19
+ declare const terminated: number;
20
+
21
+ interface ITaskErrorDetail {
22
+ type: string;
23
+ error: unknown;
24
+ }
25
+ interface ITaskError {
26
+ from: string;
27
+ details: ITaskErrorDetail[];
28
+ }
29
+ interface ITaskMonitor {
30
+ onAddError(type: string, error: unknown): void;
31
+ onStatusChange(status: TaskStatus, prevStatus: TaskStatus): void;
32
+ }
33
+ type IUnMonitorTask = () => void;
34
+ interface ITaskState {
35
+ readonly status: TaskStatus;
36
+ readonly active: boolean;
37
+ readonly alive: boolean;
38
+ readonly terminated: boolean;
39
+ }
40
+ interface ITask extends ITaskState {
41
+ /**
42
+ * Task name.
43
+ */
44
+ readonly name: string;
45
+ /**
46
+ * Task error.
47
+ */
48
+ readonly error: ITaskError | undefined;
49
+ /**
50
+ * Indicate if the task has error.
51
+ */
52
+ readonly hasError: boolean;
53
+ /**
54
+ * Start the task.
55
+ */
56
+ start(): Promise<void>;
57
+ /**
58
+ * Pause the task.
59
+ */
60
+ pause(): Promise<void>;
61
+ /**
62
+ * Resume the task.
63
+ */
64
+ resume(): Promise<void>;
65
+ /**
66
+ * Cancel the task.
67
+ */
68
+ cancel(): Promise<void>;
69
+ /**
70
+ * finish the task: run to completed no matter if the task started or not.
71
+ */
72
+ finish(): Promise<void>;
73
+ /**
74
+ * Perform a clean up.
75
+ * Will thrown an error if the task is not terminated.
76
+ */
77
+ cleanup(): void;
78
+ /**
79
+ * Register a monitor to subscribe the task changes.
80
+ * @param monitor
81
+ */
82
+ monitor(monitor: Partial<ITaskMonitor>): IUnMonitorTask;
83
+ }
84
+
85
+ declare class TaskState implements ITaskState {
86
+ readonly name: string;
87
+ private readonly _monitors;
88
+ private readonly _errorDetails;
89
+ private _status;
90
+ constructor(name: string);
91
+ get status(): TaskStatus;
92
+ get active(): boolean;
93
+ get alive(): boolean;
94
+ get terminated(): boolean;
95
+ get hasError(): boolean;
96
+ get error(): ITaskError | undefined;
97
+ set status(status: TaskStatus);
98
+ monitor(monitor: Partial<ITaskMonitor>): IUnMonitorTask;
99
+ check(nextStatus: TaskStatus): boolean;
100
+ cleanup(): void;
101
+ protected _addError(type: string, error: unknown): void;
102
+ }
103
+
104
+ declare abstract class AtomicTask extends TaskState implements ITask {
105
+ private _promise;
106
+ constructor(name: string);
107
+ start(): Promise<void>;
108
+ pause(): Promise<void>;
109
+ resume(): Promise<void>;
110
+ cancel(): Promise<void>;
111
+ finish(): Promise<void>;
112
+ protected abstract run(): Promise<void>;
113
+ }
114
+
115
+ interface IResumableTaskProps {
116
+ name: string;
117
+ strategy: TaskStrategy;
118
+ pollInterval: number;
119
+ }
120
+ declare abstract class ResumableTask extends TaskState implements ITask {
121
+ readonly strategy: TaskStrategy;
122
+ private readonly _pollInterval;
123
+ private _execution;
124
+ private _step;
125
+ constructor(props: IResumableTaskProps);
126
+ start(): Promise<void>;
127
+ pause(): Promise<void>;
128
+ resume(): Promise<void>;
129
+ cancel(): Promise<void>;
130
+ finish(): Promise<void>;
131
+ protected abstract run(): IterableIterator<Promise<void>>;
132
+ private launchStep;
133
+ private queueStep;
134
+ }
135
+
136
+ export { AtomicTask, type ITask, type ITaskError, type ITaskErrorDetail, type ITaskMonitor, type ITaskState, type IUnMonitorTask, ResumableTask, TaskState, TaskStatus, TaskStrategy, active, alive, terminated };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@guanghechen/task",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Atomic and resumable tasks",
5
+ "author": {
6
+ "name": "guanghechen",
7
+ "url": "https://github.com/guanghechen/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/guanghechen/sora/tree/@guanghechen/task@5.1.0",
12
+ "directory": "packages/task"
13
+ },
14
+ "homepage": "https://github.com/guanghechen/sora/tree/@guanghechen/task@5.1.0/packages/task#readme",
15
+ "type": "module",
16
+ "main": "./lib/cjs/index.cjs",
17
+ "module": "./lib/esm/index.mjs",
18
+ "exports": {
19
+ "import": "./lib/esm/index.mjs",
20
+ "require": "./lib/cjs/index.cjs"
21
+ },
22
+ "types": "lib/types/index.d.ts",
23
+ "source": "src/index.ts",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">= 16.0.0"
27
+ },
28
+ "files": [
29
+ "lib/",
30
+ "!lib/**/*.map",
31
+ "package.json",
32
+ "CHANGELOG.md",
33
+ "LICENSE",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "build": "rimraf lib/ && cross-env NODE_ENV=production rollup -c ../../rollup.config.mjs",
38
+ "prepublishOnly": "yarn build",
39
+ "test": "node --experimental-vm-modules ../../node_modules/.bin/jest --config ../../jest.config.mjs --rootDir ."
40
+ },
41
+ "dependencies": {
42
+ "@guanghechen/monitor": "^1.0.0-alpha.1"
43
+ },
44
+ "devDependencies": {
45
+ "cross-env": "^7.0.3",
46
+ "jest": "^29.6.4",
47
+ "rimraf": "^5.0.1",
48
+ "rollup": "^3.28.1"
49
+ },
50
+ "gitHead": "39cf17711022736f580c1dff68a535cea6ffa3ef"
51
+ }