@robosystems/core 0.11.2 → 0.11.3
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/auth-components/AuthProvider.js +166 -27
- package/auth-components/SessionWarningDialog.js +2 -2
- package/auth-components/SignInForm.d.ts +2 -1
- package/auth-components/SignInForm.js +8 -2
- package/auth-components/SignUpForm.js +4 -1
- package/auth-core/client.d.ts +8 -1
- package/auth-core/client.js +142 -100
- package/auth-core/sso.d.ts +4 -9
- package/auth-core/sso.js +18 -58
- package/auth-core/token-storage.d.ts +12 -0
- package/auth-core/token-storage.js +42 -16
- package/auth-core/types.d.ts +1 -0
- package/components/console/ConsoleContent.js +30 -4
- package/components/console/graphAwareConfig.js +27 -17
- package/components/repositories/ActiveSubscriptions.js +18 -6
- package/components/repositories/BrowseRepositories.js +17 -5
- package/components/search/SearchContent.js +30 -4
- package/contexts/graph-context.js +23 -13
- package/hooks/use-streaming-query.js +43 -28
- package/index.d.ts +5 -2
- package/index.js +6 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/sdk-errors.d.ts +79 -0
- package/lib/sdk-errors.js +148 -0
- package/package.json +1 -1
- package/task-monitoring/hooks.js +23 -13
- package/task-monitoring/operationHooks.d.ts +21 -2
- package/task-monitoring/operationHooks.js +67 -17
- package/task-monitoring/operationMonitor.d.ts +10 -1
- package/task-monitoring/operationMonitor.js +109 -57
- package/task-monitoring/taskMonitor.d.ts +15 -1
- package/task-monitoring/taskMonitor.js +120 -73
- package/task-monitoring/types.d.ts +2 -1
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import * as SDK from '@robosystems/client';
|
|
2
|
+
import { isTransientError, unwrapSdk } from '../lib/sdk-errors';
|
|
3
|
+
/** Consecutive transient status-read failures tolerated before giving up. */
|
|
4
|
+
const DEFAULT_MAX_CONSECUTIVE_ERRORS = 5;
|
|
5
|
+
/** Upper bound on the backoff between status reads after a failure. */
|
|
6
|
+
const MAX_ERROR_BACKOFF_MS = 30 * 1000;
|
|
7
|
+
/** Statuses of an operation that has not finished (API `OperationStatus`). */
|
|
8
|
+
const NON_TERMINAL_STATUSES = new Set([
|
|
9
|
+
'pending',
|
|
10
|
+
'running',
|
|
11
|
+
'awaiting_input',
|
|
12
|
+
'in_progress',
|
|
13
|
+
'retrying',
|
|
14
|
+
]);
|
|
15
|
+
/** Rejection message when watching stopped locally (`stopPolling`). */
|
|
16
|
+
export const POLLING_CANCELLED = 'Task polling was cancelled';
|
|
2
17
|
export class TaskMonitor {
|
|
3
18
|
constructor() {
|
|
4
19
|
this.activeTasks = new Map();
|
|
@@ -10,110 +25,139 @@ export class TaskMonitor {
|
|
|
10
25
|
return TaskMonitor.instance;
|
|
11
26
|
}
|
|
12
27
|
async pollTask({ taskId, onProgress, onComplete, onError, pollInterval = 2000, maxAttempts = 150, // 5 minutes at 2s intervals
|
|
13
|
-
|
|
28
|
+
maxConsecutiveErrors = DEFAULT_MAX_CONSECUTIVE_ERRORS, }) {
|
|
14
29
|
let attempts = 0;
|
|
30
|
+
let consecutiveErrors = 0;
|
|
15
31
|
const abortController = new AbortController();
|
|
32
|
+
// One poller per task: a second pollTask for the same id replaces (and
|
|
33
|
+
// stops) the first, which could otherwise never be stopped.
|
|
34
|
+
this.stopPolling(taskId);
|
|
16
35
|
this.activeTasks.set(taskId, abortController);
|
|
17
36
|
return new Promise((resolve, reject) => {
|
|
37
|
+
let settled = false;
|
|
38
|
+
let timer = null;
|
|
39
|
+
const finish = (outcome) => {
|
|
40
|
+
if (settled)
|
|
41
|
+
return;
|
|
42
|
+
settled = true;
|
|
43
|
+
if (timer)
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
if (this.activeTasks.get(taskId) === abortController) {
|
|
46
|
+
this.activeTasks.delete(taskId);
|
|
47
|
+
}
|
|
48
|
+
outcome();
|
|
49
|
+
};
|
|
50
|
+
const fail = (error, notify = true) => finish(() => {
|
|
51
|
+
if (notify)
|
|
52
|
+
onError === null || onError === void 0 ? void 0 : onError(error.message);
|
|
53
|
+
reject(error);
|
|
54
|
+
});
|
|
55
|
+
// Stopping (unmount, explicit cancel) settles the promise at once, even
|
|
56
|
+
// while a status request is in flight — its answer is then ignored.
|
|
57
|
+
abortController.signal.addEventListener('abort', () => fail(new Error(POLLING_CANCELLED), false));
|
|
58
|
+
const schedule = (delay) => {
|
|
59
|
+
if (settled)
|
|
60
|
+
return;
|
|
61
|
+
timer = setTimeout(poll, delay);
|
|
62
|
+
};
|
|
18
63
|
const poll = async () => {
|
|
64
|
+
if (settled)
|
|
65
|
+
return;
|
|
66
|
+
attempts++;
|
|
67
|
+
let status;
|
|
19
68
|
try {
|
|
20
|
-
if (abortController.signal.aborted) {
|
|
21
|
-
this.activeTasks.delete(taskId);
|
|
22
|
-
reject(new Error('Task polling was cancelled'));
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
attempts++;
|
|
26
69
|
const response = await SDK.getOperationStatus({
|
|
27
70
|
path: { operation_id: taskId },
|
|
28
71
|
});
|
|
29
|
-
if (
|
|
72
|
+
if (settled)
|
|
73
|
+
return;
|
|
74
|
+
const data = unwrapSdk(response);
|
|
75
|
+
if (!data) {
|
|
30
76
|
throw new Error('No data received from task status endpoint');
|
|
31
77
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
case 'pending':
|
|
35
|
-
case 'in_progress':
|
|
36
|
-
case 'retrying':
|
|
37
|
-
onProgress === null || onProgress === void 0 ? void 0 : onProgress(status);
|
|
38
|
-
if (attempts < maxAttempts && !abortController.signal.aborted) {
|
|
39
|
-
setTimeout(poll, pollInterval);
|
|
40
|
-
}
|
|
41
|
-
else if (attempts >= maxAttempts) {
|
|
42
|
-
this.activeTasks.delete(taskId);
|
|
43
|
-
const timeoutError = new Error(`Task polling timeout after ${attempts} attempts`);
|
|
44
|
-
onError === null || onError === void 0 ? void 0 : onError(timeoutError.message);
|
|
45
|
-
reject(timeoutError);
|
|
46
|
-
}
|
|
47
|
-
break;
|
|
48
|
-
case 'completed':
|
|
49
|
-
this.activeTasks.delete(taskId);
|
|
50
|
-
onComplete === null || onComplete === void 0 ? void 0 : onComplete(status.details || status);
|
|
51
|
-
resolve(status);
|
|
52
|
-
break;
|
|
53
|
-
case 'failed': {
|
|
54
|
-
this.activeTasks.delete(taskId);
|
|
55
|
-
const failureError = new Error(status.message || 'Task failed without error message');
|
|
56
|
-
onError === null || onError === void 0 ? void 0 : onError(failureError.message);
|
|
57
|
-
reject(failureError);
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
case 'cancelled': {
|
|
61
|
-
this.activeTasks.delete(taskId);
|
|
62
|
-
const cancelError = new Error('Task was cancelled');
|
|
63
|
-
onError === null || onError === void 0 ? void 0 : onError(cancelError.message);
|
|
64
|
-
reject(cancelError);
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
default:
|
|
68
|
-
// Handle unknown status as in_progress
|
|
69
|
-
onProgress === null || onProgress === void 0 ? void 0 : onProgress(status);
|
|
70
|
-
if (attempts < maxAttempts && !abortController.signal.aborted) {
|
|
71
|
-
setTimeout(poll, pollInterval);
|
|
72
|
-
}
|
|
73
|
-
else if (attempts >= maxAttempts) {
|
|
74
|
-
this.activeTasks.delete(taskId);
|
|
75
|
-
const unknownError = new Error(`Unknown task status: ${status.status}`);
|
|
76
|
-
onError === null || onError === void 0 ? void 0 : onError(unknownError.message);
|
|
77
|
-
reject(unknownError);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
78
|
+
status = data;
|
|
79
|
+
consecutiveErrors = 0;
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
if (settled)
|
|
83
|
+
return;
|
|
84
|
+
// A blip (network, 429, 5xx) says nothing about the task: keep
|
|
85
|
+
// polling with backoff, and only give up after several in a row.
|
|
86
|
+
if (isTransientError(error) &&
|
|
87
|
+
++consecutiveErrors < maxConsecutiveErrors) {
|
|
88
|
+
console.warn('Task status read failed, retrying:', error);
|
|
89
|
+
schedule(Math.min(pollInterval * 2 ** consecutiveErrors, MAX_ERROR_BACKOFF_MS));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
84
92
|
console.error('Task polling error:', error);
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
fail(error instanceof Error ? error : new Error('Unknown error occurred'));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
switch (status.status) {
|
|
97
|
+
case 'completed':
|
|
98
|
+
finish(() => {
|
|
99
|
+
onComplete === null || onComplete === void 0 ? void 0 : onComplete(status.details || status);
|
|
100
|
+
resolve(status);
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
case 'failed':
|
|
104
|
+
fail(new Error(status.message || 'Task failed without error message'));
|
|
105
|
+
return;
|
|
106
|
+
case 'cancelled':
|
|
107
|
+
fail(new Error('Task was cancelled'));
|
|
108
|
+
return;
|
|
109
|
+
default:
|
|
110
|
+
// Non-terminal: the API's pending / running / awaiting_input,
|
|
111
|
+
// older in_progress / retrying, and any status added later.
|
|
112
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(status);
|
|
113
|
+
if (attempts >= maxAttempts) {
|
|
114
|
+
fail(new Error(NON_TERMINAL_STATUSES.has(status.status)
|
|
115
|
+
? `Task is still ${status.status.replace(/_/g, ' ')} after ${attempts} checks. ` +
|
|
116
|
+
'It continues on the server; check back later.'
|
|
117
|
+
: `Unknown task status: ${status.status}`));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
schedule(pollInterval);
|
|
87
121
|
}
|
|
88
122
|
};
|
|
89
123
|
// Start polling
|
|
90
124
|
poll();
|
|
91
125
|
});
|
|
92
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Stop watching a task locally. The operation itself keeps running on the
|
|
129
|
+
* server — this is what leaving a page should do.
|
|
130
|
+
*/
|
|
131
|
+
stopPolling(taskId) {
|
|
132
|
+
const abortController = this.activeTasks.get(taskId);
|
|
133
|
+
if (!abortController)
|
|
134
|
+
return false;
|
|
135
|
+
this.activeTasks.delete(taskId);
|
|
136
|
+
abortController.abort();
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Cancel the operation on the server, then stop watching it. Only for an
|
|
141
|
+
* explicit user action. Returns false when the server refused — the task
|
|
142
|
+
* is then still being watched, and its real outcome still arrives.
|
|
143
|
+
*/
|
|
93
144
|
async cancelTask(taskId) {
|
|
94
145
|
try {
|
|
95
|
-
|
|
96
|
-
const abortController = this.activeTasks.get(taskId);
|
|
97
|
-
if (abortController) {
|
|
98
|
-
abortController.abort();
|
|
99
|
-
this.activeTasks.delete(taskId);
|
|
100
|
-
}
|
|
101
|
-
// Cancel task on server
|
|
102
|
-
await SDK.cancelOperation({
|
|
146
|
+
unwrapSdk(await SDK.cancelOperation({
|
|
103
147
|
path: { operation_id: taskId },
|
|
104
|
-
});
|
|
105
|
-
return true;
|
|
148
|
+
}));
|
|
106
149
|
}
|
|
107
150
|
catch (error) {
|
|
108
151
|
console.error('Failed to cancel task:', error);
|
|
109
152
|
return false;
|
|
110
153
|
}
|
|
154
|
+
this.stopPolling(taskId);
|
|
155
|
+
return true;
|
|
111
156
|
}
|
|
112
157
|
cancelAllTasks() {
|
|
113
|
-
for (const
|
|
114
|
-
|
|
158
|
+
for (const taskId of Array.from(this.activeTasks.keys())) {
|
|
159
|
+
this.stopPolling(taskId);
|
|
115
160
|
}
|
|
116
|
-
this.activeTasks.clear();
|
|
117
161
|
}
|
|
118
162
|
getActiveTasks() {
|
|
119
163
|
return Array.from(this.activeTasks.keys());
|
|
@@ -127,6 +171,9 @@ export const pollTaskStatus = (options) => {
|
|
|
127
171
|
export const cancelTask = (taskId) => {
|
|
128
172
|
return taskMonitor.cancelTask(taskId);
|
|
129
173
|
};
|
|
174
|
+
export const stopPollingTask = (taskId) => {
|
|
175
|
+
return taskMonitor.stopPolling(taskId);
|
|
176
|
+
};
|
|
130
177
|
export const getActiveTasks = () => {
|
|
131
178
|
return taskMonitor.getActiveTasks();
|
|
132
179
|
};
|
|
@@ -7,7 +7,7 @@ export interface TaskStatusResponse {
|
|
|
7
7
|
result?: any;
|
|
8
8
|
error?: string;
|
|
9
9
|
}
|
|
10
|
-
export type TaskStatus = 'pending' | 'in_progress' | '
|
|
10
|
+
export type TaskStatus = 'pending' | 'running' | 'awaiting_input' | 'in_progress' | 'retrying' | 'completed' | 'failed' | 'cancelled';
|
|
11
11
|
export interface TaskPollingOptions {
|
|
12
12
|
taskId: string;
|
|
13
13
|
onProgress?: (status: TaskStatusResponse) => void;
|
|
@@ -15,6 +15,7 @@ export interface TaskPollingOptions {
|
|
|
15
15
|
onError?: (error: string) => void;
|
|
16
16
|
pollInterval?: number;
|
|
17
17
|
maxAttempts?: number;
|
|
18
|
+
maxConsecutiveErrors?: number;
|
|
18
19
|
}
|
|
19
20
|
export interface TaskMonitorState {
|
|
20
21
|
isLoading: boolean;
|