@auser/workflow-graph-client 0.1.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/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
- package/src/index.ts +173 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @auser/workflow-graph-client — TypeScript client for the workflow-graph REST API
|
|
3
|
+
*/
|
|
4
|
+
export interface Workflow {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
trigger: string;
|
|
8
|
+
jobs: Job[];
|
|
9
|
+
}
|
|
10
|
+
export interface Job {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
status: string;
|
|
14
|
+
command: string;
|
|
15
|
+
duration_secs?: number;
|
|
16
|
+
started_at?: number;
|
|
17
|
+
depends_on: string[];
|
|
18
|
+
output?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface LogChunk {
|
|
21
|
+
workflow_id: string;
|
|
22
|
+
job_id: string;
|
|
23
|
+
sequence: number;
|
|
24
|
+
data: string;
|
|
25
|
+
timestamp_ms: number;
|
|
26
|
+
stream: 'stdout' | 'stderr';
|
|
27
|
+
}
|
|
28
|
+
export interface WorkerInfo {
|
|
29
|
+
worker_id: string;
|
|
30
|
+
labels: string[];
|
|
31
|
+
registered_at_ms: number;
|
|
32
|
+
last_heartbeat_ms: number;
|
|
33
|
+
current_job: string | null;
|
|
34
|
+
status: 'idle' | 'busy' | 'offline';
|
|
35
|
+
}
|
|
36
|
+
/** Error thrown when the API returns a non-OK response. */
|
|
37
|
+
export declare class WorkflowApiError extends Error {
|
|
38
|
+
readonly status: number;
|
|
39
|
+
readonly statusText: string;
|
|
40
|
+
constructor(message: string, status: number, statusText: string);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Client for the workflow-graph REST API.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const client = new WorkflowClient('http://localhost:3000');
|
|
48
|
+
* const workflows = await client.listWorkflows();
|
|
49
|
+
* await client.runWorkflow(workflows[0].id);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare class WorkflowClient {
|
|
53
|
+
private baseUrl;
|
|
54
|
+
constructor(baseUrl: string);
|
|
55
|
+
listWorkflows(): Promise<Workflow[]>;
|
|
56
|
+
getStatus(id: string): Promise<Workflow>;
|
|
57
|
+
createWorkflow(workflow: Omit<Workflow, 'id'>): Promise<Workflow>;
|
|
58
|
+
runWorkflow(id: string): Promise<void>;
|
|
59
|
+
cancelWorkflow(id: string): Promise<void>;
|
|
60
|
+
getJobLogs(workflowId: string, jobId: string): Promise<LogChunk[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Stream job logs via SSE. Returns an async iterable of log chunks.
|
|
63
|
+
*/
|
|
64
|
+
streamLogs(workflowId: string, jobId: string): AsyncIterable<LogChunk>;
|
|
65
|
+
listWorkers(): Promise<WorkerInfo[]>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAED,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,2DAA2D;AAC3D,qBAAa,gBAAiB,SAAQ,KAAK;aAGvB,MAAM,EAAE,MAAM;aACd,UAAU,EAAE,MAAM;gBAFlC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM;CAKrC;AAcD;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM;IAE7B,aAAa,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAMpC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMxC,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IAUjE,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtC,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzC,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAQxE;;OAEG;IACI,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC;IA2CvE,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;CAK3C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @auser/workflow-graph-client — TypeScript client for the workflow-graph REST API
|
|
3
|
+
*/
|
|
4
|
+
/** Error thrown when the API returns a non-OK response. */
|
|
5
|
+
export class WorkflowApiError extends Error {
|
|
6
|
+
constructor(message, status, statusText) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.statusText = statusText;
|
|
10
|
+
this.name = 'WorkflowApiError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Assert response is OK, throw WorkflowApiError otherwise. */
|
|
14
|
+
async function assertOk(res, context) {
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
const body = await res.text().catch(() => '');
|
|
17
|
+
throw new WorkflowApiError(`${context}: ${res.status} ${res.statusText}${body ? ` — ${body}` : ''}`, res.status, res.statusText);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Client for the workflow-graph REST API.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const client = new WorkflowClient('http://localhost:3000');
|
|
26
|
+
* const workflows = await client.listWorkflows();
|
|
27
|
+
* await client.runWorkflow(workflows[0].id);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class WorkflowClient {
|
|
31
|
+
constructor(baseUrl) {
|
|
32
|
+
this.baseUrl = baseUrl;
|
|
33
|
+
}
|
|
34
|
+
async listWorkflows() {
|
|
35
|
+
const res = await fetch(`${this.baseUrl}/api/workflows`);
|
|
36
|
+
await assertOk(res, 'listWorkflows');
|
|
37
|
+
return res.json();
|
|
38
|
+
}
|
|
39
|
+
async getStatus(id) {
|
|
40
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/status`);
|
|
41
|
+
await assertOk(res, `getStatus(${id})`);
|
|
42
|
+
return res.json();
|
|
43
|
+
}
|
|
44
|
+
async createWorkflow(workflow) {
|
|
45
|
+
const res = await fetch(`${this.baseUrl}/api/workflows`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: { 'Content-Type': 'application/json' },
|
|
48
|
+
body: JSON.stringify(workflow),
|
|
49
|
+
});
|
|
50
|
+
await assertOk(res, 'createWorkflow');
|
|
51
|
+
return res.json();
|
|
52
|
+
}
|
|
53
|
+
async runWorkflow(id) {
|
|
54
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/run`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
});
|
|
57
|
+
await assertOk(res, `runWorkflow(${id})`);
|
|
58
|
+
}
|
|
59
|
+
async cancelWorkflow(id) {
|
|
60
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/cancel`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
});
|
|
63
|
+
await assertOk(res, `cancelWorkflow(${id})`);
|
|
64
|
+
}
|
|
65
|
+
async getJobLogs(workflowId, jobId) {
|
|
66
|
+
const wfId = encodeURIComponent(workflowId);
|
|
67
|
+
const jId = encodeURIComponent(jobId);
|
|
68
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${wfId}/jobs/${jId}/logs`);
|
|
69
|
+
await assertOk(res, `getJobLogs(${workflowId}, ${jobId})`);
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stream job logs via SSE. Returns an async iterable of log chunks.
|
|
74
|
+
*/
|
|
75
|
+
async *streamLogs(workflowId, jobId) {
|
|
76
|
+
const wfId = encodeURIComponent(workflowId);
|
|
77
|
+
const jId = encodeURIComponent(jobId);
|
|
78
|
+
const url = `${this.baseUrl}/api/workflows/${wfId}/jobs/${jId}/logs/stream`;
|
|
79
|
+
const eventSource = new EventSource(url);
|
|
80
|
+
const chunks = [];
|
|
81
|
+
let resolve = null;
|
|
82
|
+
let done = false;
|
|
83
|
+
eventSource.addEventListener('log', (event) => {
|
|
84
|
+
chunks.push(JSON.parse(event.data));
|
|
85
|
+
if (resolve) {
|
|
86
|
+
resolve();
|
|
87
|
+
resolve = null;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
eventSource.addEventListener('error', () => {
|
|
91
|
+
done = true;
|
|
92
|
+
eventSource.close();
|
|
93
|
+
if (resolve) {
|
|
94
|
+
resolve();
|
|
95
|
+
resolve = null;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
while (!done) {
|
|
100
|
+
while (chunks.length > 0) {
|
|
101
|
+
yield chunks.shift();
|
|
102
|
+
}
|
|
103
|
+
if (!done) {
|
|
104
|
+
await new Promise((r) => {
|
|
105
|
+
resolve = r;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
eventSource.close();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async listWorkers() {
|
|
115
|
+
const res = await fetch(`${this.baseUrl}/api/workers`);
|
|
116
|
+
await assertOk(res, 'listWorkers');
|
|
117
|
+
return res.json();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auser/workflow-graph-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript client for the workflow-graph REST API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": ["dist/", "src/", "README.md"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["workflow", "dag", "graph", "client", "rest-api", "github-actions"],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/auser/workflow-graph",
|
|
18
|
+
"directory": "packages/client"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://auser.github.io/workflow-graph/",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @auser/workflow-graph-client — TypeScript client for the workflow-graph REST API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface Workflow {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
trigger: string;
|
|
9
|
+
jobs: Job[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Job {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
status: string;
|
|
16
|
+
command: string;
|
|
17
|
+
duration_secs?: number;
|
|
18
|
+
started_at?: number;
|
|
19
|
+
depends_on: string[];
|
|
20
|
+
output?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LogChunk {
|
|
24
|
+
workflow_id: string;
|
|
25
|
+
job_id: string;
|
|
26
|
+
sequence: number;
|
|
27
|
+
data: string;
|
|
28
|
+
timestamp_ms: number;
|
|
29
|
+
stream: 'stdout' | 'stderr';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface WorkerInfo {
|
|
33
|
+
worker_id: string;
|
|
34
|
+
labels: string[];
|
|
35
|
+
registered_at_ms: number;
|
|
36
|
+
last_heartbeat_ms: number;
|
|
37
|
+
current_job: string | null;
|
|
38
|
+
status: 'idle' | 'busy' | 'offline';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Error thrown when the API returns a non-OK response. */
|
|
42
|
+
export class WorkflowApiError extends Error {
|
|
43
|
+
constructor(
|
|
44
|
+
message: string,
|
|
45
|
+
public readonly status: number,
|
|
46
|
+
public readonly statusText: string,
|
|
47
|
+
) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = 'WorkflowApiError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Assert response is OK, throw WorkflowApiError otherwise. */
|
|
54
|
+
async function assertOk(res: Response, context: string): Promise<void> {
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const body = await res.text().catch(() => '');
|
|
57
|
+
throw new WorkflowApiError(
|
|
58
|
+
`${context}: ${res.status} ${res.statusText}${body ? ` — ${body}` : ''}`,
|
|
59
|
+
res.status,
|
|
60
|
+
res.statusText,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Client for the workflow-graph REST API.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const client = new WorkflowClient('http://localhost:3000');
|
|
71
|
+
* const workflows = await client.listWorkflows();
|
|
72
|
+
* await client.runWorkflow(workflows[0].id);
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class WorkflowClient {
|
|
76
|
+
constructor(private baseUrl: string) {}
|
|
77
|
+
|
|
78
|
+
async listWorkflows(): Promise<Workflow[]> {
|
|
79
|
+
const res = await fetch(`${this.baseUrl}/api/workflows`);
|
|
80
|
+
await assertOk(res, 'listWorkflows');
|
|
81
|
+
return res.json() as Promise<Workflow[]>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async getStatus(id: string): Promise<Workflow> {
|
|
85
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/status`);
|
|
86
|
+
await assertOk(res, `getStatus(${id})`);
|
|
87
|
+
return res.json() as Promise<Workflow>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async createWorkflow(workflow: Omit<Workflow, 'id'>): Promise<Workflow> {
|
|
91
|
+
const res = await fetch(`${this.baseUrl}/api/workflows`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
body: JSON.stringify(workflow),
|
|
95
|
+
});
|
|
96
|
+
await assertOk(res, 'createWorkflow');
|
|
97
|
+
return res.json() as Promise<Workflow>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async runWorkflow(id: string): Promise<void> {
|
|
101
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/run`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
});
|
|
104
|
+
await assertOk(res, `runWorkflow(${id})`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async cancelWorkflow(id: string): Promise<void> {
|
|
108
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${encodeURIComponent(id)}/cancel`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
});
|
|
111
|
+
await assertOk(res, `cancelWorkflow(${id})`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getJobLogs(workflowId: string, jobId: string): Promise<LogChunk[]> {
|
|
115
|
+
const wfId = encodeURIComponent(workflowId);
|
|
116
|
+
const jId = encodeURIComponent(jobId);
|
|
117
|
+
const res = await fetch(`${this.baseUrl}/api/workflows/${wfId}/jobs/${jId}/logs`);
|
|
118
|
+
await assertOk(res, `getJobLogs(${workflowId}, ${jobId})`);
|
|
119
|
+
return res.json() as Promise<LogChunk[]>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Stream job logs via SSE. Returns an async iterable of log chunks.
|
|
124
|
+
*/
|
|
125
|
+
async *streamLogs(workflowId: string, jobId: string): AsyncIterable<LogChunk> {
|
|
126
|
+
const wfId = encodeURIComponent(workflowId);
|
|
127
|
+
const jId = encodeURIComponent(jobId);
|
|
128
|
+
const url = `${this.baseUrl}/api/workflows/${wfId}/jobs/${jId}/logs/stream`;
|
|
129
|
+
const eventSource = new EventSource(url);
|
|
130
|
+
|
|
131
|
+
const chunks: LogChunk[] = [];
|
|
132
|
+
let resolve: (() => void) | null = null;
|
|
133
|
+
let done = false;
|
|
134
|
+
|
|
135
|
+
eventSource.addEventListener('log', (event: MessageEvent) => {
|
|
136
|
+
chunks.push(JSON.parse(event.data) as LogChunk);
|
|
137
|
+
if (resolve) {
|
|
138
|
+
resolve();
|
|
139
|
+
resolve = null;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
eventSource.addEventListener('error', () => {
|
|
144
|
+
done = true;
|
|
145
|
+
eventSource.close();
|
|
146
|
+
if (resolve) {
|
|
147
|
+
resolve();
|
|
148
|
+
resolve = null;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
while (!done) {
|
|
154
|
+
while (chunks.length > 0) {
|
|
155
|
+
yield chunks.shift()!;
|
|
156
|
+
}
|
|
157
|
+
if (!done) {
|
|
158
|
+
await new Promise<void>((r) => {
|
|
159
|
+
resolve = r;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
eventSource.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async listWorkers(): Promise<WorkerInfo[]> {
|
|
169
|
+
const res = await fetch(`${this.baseUrl}/api/workers`);
|
|
170
|
+
await assertOk(res, 'listWorkers');
|
|
171
|
+
return res.json() as Promise<WorkerInfo[]>;
|
|
172
|
+
}
|
|
173
|
+
}
|