@api-client/core 0.5.24 → 0.5.27
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/build/src/events/BaseEvents.d.ts +1 -1
- package/build/src/events/BaseEvents.js.map +1 -1
- package/build/src/events/EventTypes.d.ts +16 -0
- package/build/src/events/EventTypes.js +2 -0
- package/build/src/events/EventTypes.js.map +1 -1
- package/build/src/events/Events.d.ts +16 -0
- package/build/src/events/Events.js +2 -0
- package/build/src/events/Events.js.map +1 -1
- package/build/src/events/transport/TransportEventTypes.d.ts +31 -0
- package/build/src/events/transport/TransportEventTypes.js +36 -0
- package/build/src/events/transport/TransportEventTypes.js.map +1 -0
- package/build/src/events/transport/TransportEvents.d.ts +77 -0
- package/build/src/events/transport/TransportEvents.js +91 -0
- package/build/src/events/transport/TransportEvents.js.map +1 -0
- package/build/src/lib/transformers/PayloadSerializer.d.ts +11 -5
- package/build/src/lib/transformers/PayloadSerializer.js +36 -10
- package/build/src/lib/transformers/PayloadSerializer.js.map +1 -1
- package/build/src/mocking/ProjectMock.d.ts +2 -0
- package/build/src/mocking/ProjectMock.js +3 -0
- package/build/src/mocking/ProjectMock.js.map +1 -1
- package/build/src/mocking/lib/Url.d.ts +12 -0
- package/build/src/mocking/lib/Url.js +31 -0
- package/build/src/mocking/lib/Url.js.map +1 -0
- package/build/src/models/RequestConfig.d.ts +10 -0
- package/build/src/models/RequestConfig.js +13 -1
- package/build/src/models/RequestConfig.js.map +1 -1
- package/build/src/models/SerializablePayload.js +1 -6
- package/build/src/models/SerializablePayload.js.map +1 -1
- package/build/src/runtime/http-engine/CoreEngine.js +1 -1
- package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
- package/build/src/runtime/http-engine/HttpEngine.d.ts +12 -0
- package/build/src/runtime/http-engine/HttpEngine.js +34 -0
- package/build/src/runtime/http-engine/HttpEngine.js.map +1 -1
- package/build/src/runtime/node/InteropInterfaces.d.ts +5 -0
- package/build/src/runtime/node/ProjectParallelRunner.d.ts +20 -0
- package/build/src/runtime/node/ProjectParallelRunner.js +69 -1
- package/build/src/runtime/node/ProjectParallelRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRequestRunner.d.ts +8 -0
- package/build/src/runtime/node/ProjectRequestRunner.js +19 -0
- package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRunner.d.ts +25 -0
- package/build/src/runtime/node/ProjectRunner.js +55 -0
- package/build/src/runtime/node/ProjectRunner.js.map +1 -1
- package/build/src/runtime/node/ProjectRunnerWorker.js +3 -0
- package/build/src/runtime/node/ProjectRunnerWorker.js.map +1 -1
- package/build/src/runtime/node/ProjectSerialRunner.js +3 -0
- package/build/src/runtime/node/ProjectSerialRunner.js.map +1 -1
- package/build/src/runtime/node/RequestFactory.d.ts +6 -0
- package/build/src/runtime/node/RequestFactory.js +10 -1
- package/build/src/runtime/node/RequestFactory.js.map +1 -1
- package/build/src/runtime/node/enums.d.ts +8 -0
- package/build/src/runtime/node/enums.js +10 -0
- package/build/src/runtime/node/enums.js.map +1 -0
- package/package.json +1 -1
- package/src/events/BaseEvents.ts +1 -1
- package/src/events/EventTypes.ts +2 -0
- package/src/events/Events.ts +2 -0
- package/src/events/transport/TransportEventTypes.ts +37 -0
- package/src/events/transport/TransportEvents.ts +116 -0
- package/src/lib/transformers/PayloadSerializer.ts +38 -11
- package/src/mocking/ProjectMock.ts +3 -0
- package/src/mocking/lib/Url.ts +35 -0
- package/src/models/RequestConfig.ts +19 -1
- package/src/models/SerializablePayload.ts +1 -5
- package/src/runtime/http-engine/CoreEngine.ts +1 -1
- package/src/runtime/http-engine/HttpEngine.ts +39 -0
- package/src/runtime/node/InteropInterfaces.ts +6 -0
- package/src/runtime/node/ProjectParallelRunner.ts +75 -1
- package/src/runtime/node/ProjectRequestRunner.ts +22 -0
- package/src/runtime/node/ProjectRunner.ts +63 -0
- package/src/runtime/node/ProjectRunnerWorker.ts +3 -0
- package/src/runtime/node/ProjectSerialRunner.ts +3 -2
- package/src/runtime/node/RequestFactory.ts +11 -1
- package/src/runtime/node/enums.ts +8 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { IRequestAuthorization } from "../../models/RequestAuthorization.js";
|
|
2
|
+
import { IHttpRequest } from "../../models/HttpRequest.js";
|
|
3
|
+
import { IRequestBaseConfig } from "../../models/RequestConfig.js";
|
|
4
|
+
import { IRequestLog } from '../../models/RequestLog.js';
|
|
5
|
+
import { HttpProject } from "../../models/HttpProject.js";
|
|
6
|
+
import { ContextEvent } from "../BaseEvents.js";
|
|
7
|
+
import { TransportEventTypes } from "./TransportEventTypes.js";
|
|
8
|
+
import { IProjectRunnerOptions } from "../../runtime/node/InteropInterfaces.js";
|
|
9
|
+
import { IProjectExecutionLog } from "../../runtime/reporters/Reporter.js";
|
|
10
|
+
|
|
11
|
+
export interface ICoreRequestDetail {
|
|
12
|
+
request: IHttpRequest;
|
|
13
|
+
authorization?: IRequestAuthorization[];
|
|
14
|
+
config?: IRequestBaseConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IHttpRequestDetail {
|
|
18
|
+
request: IHttpRequest;
|
|
19
|
+
init?: RequestInit;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IProjectRequestDetail {
|
|
23
|
+
project: HttpProject | string;
|
|
24
|
+
opts: IProjectRunnerOptions;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* eslint-disable no-unused-vars */
|
|
28
|
+
export const TransportEvent = Object.freeze({
|
|
29
|
+
/**
|
|
30
|
+
* Transport via the CoreEngine.
|
|
31
|
+
*/
|
|
32
|
+
Core: Object.freeze({
|
|
33
|
+
/**
|
|
34
|
+
* Sends a single request without a context of a project.
|
|
35
|
+
*
|
|
36
|
+
* @param target The events target.
|
|
37
|
+
* @param request The request definition
|
|
38
|
+
* @param authorization When known, a list of authorization configuration to apply to the request.
|
|
39
|
+
* @param config Optional request configuration.
|
|
40
|
+
* @returns The execution log or `undefined` when the event was not handled.
|
|
41
|
+
*/
|
|
42
|
+
send: async (target: EventTarget, request: IHttpRequest, authorization?: IRequestAuthorization[], config?: IRequestBaseConfig): Promise<IRequestLog | undefined> => {
|
|
43
|
+
const e = new ContextEvent<ICoreRequestDetail, IRequestLog>(TransportEventTypes.Core.send, {
|
|
44
|
+
request,
|
|
45
|
+
authorization,
|
|
46
|
+
config
|
|
47
|
+
});
|
|
48
|
+
target.dispatchEvent(e);
|
|
49
|
+
return e.detail.result;
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
/**
|
|
53
|
+
* Transport via the native platform's bindings.
|
|
54
|
+
*/
|
|
55
|
+
Http: Object.freeze({
|
|
56
|
+
/**
|
|
57
|
+
* Sends the request outside the Core engine, most probably using Fetch API.
|
|
58
|
+
* Note, CORS may apply to the request.
|
|
59
|
+
*
|
|
60
|
+
* @param target The events target
|
|
61
|
+
* @param request The base request definition.
|
|
62
|
+
* @param init Optional request init options compatible with the Fetch API.
|
|
63
|
+
* @returns Compatible with the Fetch API Response object or `undefined` when the event was not handled.
|
|
64
|
+
*/
|
|
65
|
+
send: async (target: EventTarget, request: IHttpRequest, init?: RequestInit): Promise<Response | undefined> => {
|
|
66
|
+
const e = new ContextEvent<IHttpRequestDetail, Response>(TransportEventTypes.Http.send, {
|
|
67
|
+
request,
|
|
68
|
+
init,
|
|
69
|
+
});
|
|
70
|
+
target.dispatchEvent(e);
|
|
71
|
+
return e.detail.result;
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
|
|
75
|
+
// project runner
|
|
76
|
+
Project: Object.freeze({
|
|
77
|
+
/**
|
|
78
|
+
* For both a request or a folder (since it's all single configuration.)
|
|
79
|
+
*
|
|
80
|
+
* @param target The events target
|
|
81
|
+
* @param project The instance of a project or an id of the project to execute. The current user has to be already authenticated.
|
|
82
|
+
* @param opts The project execution options.
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
85
|
+
send: async (target: EventTarget, project: HttpProject | string, opts: IProjectRunnerOptions): Promise<IProjectExecutionLog | undefined> => {
|
|
86
|
+
const e = new ContextEvent<IProjectRequestDetail, IProjectExecutionLog>(TransportEventTypes.Project.send, {
|
|
87
|
+
project,
|
|
88
|
+
opts,
|
|
89
|
+
});
|
|
90
|
+
target.dispatchEvent(e);
|
|
91
|
+
return e.detail.result;
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
// web sockets.
|
|
96
|
+
Ws: Object.freeze({
|
|
97
|
+
/**
|
|
98
|
+
* Informs to make a connection. Used by web sockets.
|
|
99
|
+
*/
|
|
100
|
+
connect: async (target: EventTarget): Promise<any> => {
|
|
101
|
+
throw new Error(`Not yet implemented`);
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Informs to close the current connection. Used by web sockets.
|
|
105
|
+
*/
|
|
106
|
+
disconnect: async (target: EventTarget): Promise<any> => {
|
|
107
|
+
throw new Error(`Not yet implemented`);
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* Informs to send a data on the current connection. Used by web sockets.
|
|
111
|
+
*/
|
|
112
|
+
send: async (target: EventTarget): Promise<any> => {
|
|
113
|
+
throw new Error(`Not yet implemented`);
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
@@ -2,6 +2,7 @@ import { blobToDataUrl } from './Utils.js';
|
|
|
2
2
|
|
|
3
3
|
export type PayloadTypes = 'string' | 'file' | 'blob' | 'buffer' | 'arraybuffer' | 'formdata' | 'x-www-form-urlencoded';
|
|
4
4
|
export type DeserializedPayload = string | Blob | File | FormData | Buffer | ArrayBuffer | undefined;
|
|
5
|
+
export const SupportedPayloadTypes: PayloadTypes[] = ['string', 'file', 'blob', 'buffer', 'arraybuffer', 'formdata', 'x-www-form-urlencoded'];
|
|
5
6
|
|
|
6
7
|
export interface IMultipartBody {
|
|
7
8
|
/**
|
|
@@ -64,15 +65,41 @@ export const hasBuffer: boolean = typeof Buffer === 'function';
|
|
|
64
65
|
|
|
65
66
|
export class PayloadSerializer {
|
|
66
67
|
/**
|
|
67
|
-
*
|
|
68
|
+
* Checked whether the passed payload can be safely stored in the data store.
|
|
69
|
+
* @param payload The value to test.
|
|
68
70
|
*/
|
|
69
|
-
static
|
|
70
|
-
if (
|
|
71
|
-
|
|
71
|
+
static isSafePayload(payload: any): boolean {
|
|
72
|
+
if (payload === undefined || payload === null) {
|
|
73
|
+
// both values should be stored correctly
|
|
74
|
+
return true;
|
|
72
75
|
}
|
|
73
76
|
if (typeof payload === 'string') {
|
|
74
|
-
return
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
// checks whether the payload is already serialized./
|
|
80
|
+
const typed = payload as ISafePayload;
|
|
81
|
+
if (typed.type && SupportedPayloadTypes.includes(typed.type)) {
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Transforms the payload into a data store safe object.
|
|
89
|
+
*/
|
|
90
|
+
static async serialize(payload: DeserializedPayload): Promise<ISafePayload | string | undefined> {
|
|
91
|
+
if (PayloadSerializer.isSafePayload(payload)) {
|
|
92
|
+
if (payload === null) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return (payload as unknown) as ISafePayload | string | undefined;
|
|
75
96
|
}
|
|
97
|
+
// if (payload === undefined || payload === null) {
|
|
98
|
+
// return undefined;
|
|
99
|
+
// }
|
|
100
|
+
// if (typeof payload === 'string') {
|
|
101
|
+
// return payload;
|
|
102
|
+
// }
|
|
76
103
|
if (hasBlob && payload instanceof Blob) {
|
|
77
104
|
return PayloadSerializer.stringifyBlob(payload);
|
|
78
105
|
}
|
|
@@ -129,8 +156,8 @@ export class PayloadSerializer {
|
|
|
129
156
|
/**
|
|
130
157
|
* When the passed argument is an ArrayBuffer it creates an object describing the object in a safe to store object.
|
|
131
158
|
*
|
|
132
|
-
* @param
|
|
133
|
-
* @returns
|
|
159
|
+
* @param payload
|
|
160
|
+
* @returns The buffer metadata or undefined if the passed argument is not an ArrayBuffer.
|
|
134
161
|
*/
|
|
135
162
|
static stringifyArrayBuffer(payload: ArrayBuffer): ISafePayload | undefined {
|
|
136
163
|
if (payload.byteLength) {
|
|
@@ -164,9 +191,9 @@ export class PayloadSerializer {
|
|
|
164
191
|
/**
|
|
165
192
|
* Transforms a FormData entry into a safe-to-store text entry
|
|
166
193
|
*
|
|
167
|
-
* @param
|
|
168
|
-
* @param
|
|
169
|
-
* @
|
|
194
|
+
* @param name The part name
|
|
195
|
+
* @param file The part value
|
|
196
|
+
* @returns Transformed FormData part to a datastore safe entry.
|
|
170
197
|
*/
|
|
171
198
|
static async serializeFormDataEntry(name: string, file: string | File): Promise<IMultipartBody> {
|
|
172
199
|
if (typeof file === 'string') {
|
|
@@ -202,7 +229,7 @@ export class PayloadSerializer {
|
|
|
202
229
|
* Restores the payload into its original format.
|
|
203
230
|
*/
|
|
204
231
|
static async deserialize(payload: ISafePayload | string | undefined): Promise<DeserializedPayload> {
|
|
205
|
-
if (
|
|
232
|
+
if (payload === undefined || payload === null) {
|
|
206
233
|
return undefined;
|
|
207
234
|
}
|
|
208
235
|
if (typeof payload === 'string') {
|
|
@@ -3,6 +3,7 @@ import { Request } from './lib/Request.js';
|
|
|
3
3
|
import { Response } from './lib/Response.js';
|
|
4
4
|
import { User } from './lib/User.js';
|
|
5
5
|
import { History } from './lib/History.js';
|
|
6
|
+
import { Url } from './lib/Url.js';
|
|
6
7
|
|
|
7
8
|
export { IRequestLogInit } from './lib/Request.js';
|
|
8
9
|
export { IResponseInit } from './lib/Response.js';
|
|
@@ -14,6 +15,7 @@ export class ProjectMock extends DataMock {
|
|
|
14
15
|
response: Response;
|
|
15
16
|
user: User;
|
|
16
17
|
history: History;
|
|
18
|
+
url: Url;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* @param init The library init options.
|
|
@@ -24,5 +26,6 @@ export class ProjectMock extends DataMock {
|
|
|
24
26
|
this.response = new Response(init);
|
|
25
27
|
this.user = new User(init);
|
|
26
28
|
this.history = new History(init);
|
|
29
|
+
this.url = new Url(init);
|
|
27
30
|
}
|
|
28
31
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DataMockInit, Internet, Types } from "@pawel-up/data-mock";
|
|
2
|
+
import { IUrl } from "../../models/Url.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Mocks the URL data.
|
|
6
|
+
*/
|
|
7
|
+
export class Url {
|
|
8
|
+
types: Types;
|
|
9
|
+
internet: Internet;
|
|
10
|
+
|
|
11
|
+
constructor(init: DataMockInit={}) {
|
|
12
|
+
this.types = new Types(init.seed);
|
|
13
|
+
this.internet = new Internet(init);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
url(): IUrl {
|
|
17
|
+
const date = this.types.datetime();
|
|
18
|
+
const result: IUrl = {
|
|
19
|
+
url: this.internet.uri(),
|
|
20
|
+
cnt: this.types.number({ min: 0 }),
|
|
21
|
+
time: date.getTime(),
|
|
22
|
+
};
|
|
23
|
+
date.setHours(0, 0, 0, 0);
|
|
24
|
+
result.midnight = date.getTime();
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
urls(size=25): IUrl[] {
|
|
29
|
+
const result: IUrl[] = [];
|
|
30
|
+
for (let i = 0; i < size; i++) {
|
|
31
|
+
result.push(this.url());
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -59,6 +59,12 @@ export interface IRequestBaseConfig {
|
|
|
59
59
|
* Whether the processor should validate certificates.
|
|
60
60
|
*/
|
|
61
61
|
validateCertificates?: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Optional signal from an `AbortController`.
|
|
65
|
+
* This is populated only when executing a request. This value is opaque for the data store.
|
|
66
|
+
*/
|
|
67
|
+
signal?: AbortSignal;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -150,6 +156,12 @@ export class RequestConfig {
|
|
|
150
156
|
*/
|
|
151
157
|
sentMessageLimit?: number;
|
|
152
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Optional signal from an `AbortController`.
|
|
161
|
+
* This is populated only when executing a request. This value is opaque for the data store.
|
|
162
|
+
*/
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
|
|
153
165
|
static withDefaults(): RequestConfig {
|
|
154
166
|
return new RequestConfig({
|
|
155
167
|
kind: Kind,
|
|
@@ -209,7 +221,7 @@ export class RequestConfig {
|
|
|
209
221
|
new(init: IRequestConfig): void {
|
|
210
222
|
const {
|
|
211
223
|
enabled, followRedirects, ignoreSessionCookies, validateCertificates, defaultHeaders, timeout, hosts, variables,
|
|
212
|
-
defaultAccept, defaultUserAgent, proxy, proxyPassword, proxyUsername, sentMessageLimit,
|
|
224
|
+
defaultAccept, defaultUserAgent, proxy, proxyPassword, proxyUsername, sentMessageLimit, signal,
|
|
213
225
|
} = init;
|
|
214
226
|
this.kind = Kind;
|
|
215
227
|
if (typeof enabled === 'boolean') {
|
|
@@ -282,6 +294,11 @@ export class RequestConfig {
|
|
|
282
294
|
} else {
|
|
283
295
|
this.sentMessageLimit = undefined;
|
|
284
296
|
}
|
|
297
|
+
if (signal) {
|
|
298
|
+
this.signal = signal;
|
|
299
|
+
} else {
|
|
300
|
+
this.signal = undefined;
|
|
301
|
+
}
|
|
285
302
|
}
|
|
286
303
|
|
|
287
304
|
toJSON(): IRequestConfig {
|
|
@@ -310,6 +327,7 @@ export class RequestConfig {
|
|
|
310
327
|
if (Array.isArray(this.variables)) {
|
|
311
328
|
result.variables = this.variables.map(i => i.toJSON());
|
|
312
329
|
}
|
|
330
|
+
// DO NOT put the `signal` here.
|
|
313
331
|
return result;
|
|
314
332
|
}
|
|
315
333
|
}
|
|
@@ -17,11 +17,7 @@ export class SerializablePayload {
|
|
|
17
17
|
*/
|
|
18
18
|
async writePayload(message: unknown): Promise<void> {
|
|
19
19
|
this._sourcePayload = message;
|
|
20
|
-
|
|
21
|
-
this.payload = message;
|
|
22
|
-
} else {
|
|
23
|
-
this.payload = await PayloadSerializer.serialize(message as DeserializedPayload);
|
|
24
|
-
}
|
|
20
|
+
this.payload = await PayloadSerializer.serialize(message as DeserializedPayload);
|
|
25
21
|
}
|
|
26
22
|
|
|
27
23
|
/**
|
|
@@ -191,6 +191,31 @@ export abstract class HttpEngine extends EventEmitter {
|
|
|
191
191
|
protected mainRejecter?: (err: SerializableError) => void;
|
|
192
192
|
[mainPromiseSymbol]?: Promise<IRequestLog>;
|
|
193
193
|
|
|
194
|
+
protected _signal?: AbortSignal;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The abort signal to set on this request.
|
|
198
|
+
* Aborts the request when the signal fires.
|
|
199
|
+
* @type {(AbortSignal | undefined)}
|
|
200
|
+
*/
|
|
201
|
+
get signal(): AbortSignal | undefined {
|
|
202
|
+
return this._signal;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
set signal(value: AbortSignal | undefined) {
|
|
206
|
+
const old = this._signal;
|
|
207
|
+
if (old === value) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
this._signal = value;
|
|
211
|
+
if (old) {
|
|
212
|
+
old.removeEventListener('abort', this._abortHandler);
|
|
213
|
+
}
|
|
214
|
+
if (value) {
|
|
215
|
+
value.addEventListener('abort', this._abortHandler);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
194
219
|
constructor(request: IHttpRequest, opts: HttpEngineOptions = {}) {
|
|
195
220
|
super();
|
|
196
221
|
this.request = new HttpRequest({ ...request });
|
|
@@ -199,6 +224,11 @@ export abstract class HttpEngine extends EventEmitter {
|
|
|
199
224
|
this.sentRequest = new SentRequest({ ...request, startTime: Date.now() });
|
|
200
225
|
this.uri = this.readUrl(request.url);
|
|
201
226
|
this.hostHeader = RequestUtils.getHostHeader(request.url);
|
|
227
|
+
|
|
228
|
+
this._abortHandler = this._abortHandler.bind(this);
|
|
229
|
+
if (opts.signal) {
|
|
230
|
+
this.signal = opts.signal;
|
|
231
|
+
}
|
|
202
232
|
}
|
|
203
233
|
|
|
204
234
|
/**
|
|
@@ -245,6 +275,15 @@ export abstract class HttpEngine extends EventEmitter {
|
|
|
245
275
|
this.socket = undefined;
|
|
246
276
|
}
|
|
247
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Handler for the `abort` event on the `AbortSignal`.
|
|
280
|
+
*/
|
|
281
|
+
protected _abortHandler(): void {
|
|
282
|
+
const e = new SerializableError('Request aborted', 3);
|
|
283
|
+
this._errorRequest(e);
|
|
284
|
+
this.abort();
|
|
285
|
+
}
|
|
286
|
+
|
|
248
287
|
/**
|
|
249
288
|
* Sends the request.
|
|
250
289
|
*/
|
|
@@ -118,4 +118,10 @@ export interface IProjectRunnerOptions {
|
|
|
118
118
|
* When not set it does not read system variables.
|
|
119
119
|
*/
|
|
120
120
|
variables?: boolean | string[] | Record<string, string>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Optional signal from an `AbortController`.
|
|
124
|
+
* It aborts the execution when the ``abort` event is dispatched.
|
|
125
|
+
*/
|
|
126
|
+
signal?: AbortSignal;
|
|
121
127
|
}
|
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
import { HttpProject } from '../../models/HttpProject.js';
|
|
6
6
|
import { IProjectExecutionLog, IProjectExecutionIteration } from '../reporters/Reporter.js';
|
|
7
7
|
import { BaseRunner } from './BaseRunner.js';
|
|
8
|
+
import { State } from './enums.js';
|
|
8
9
|
import { IProjectParallelRunnerOptions, IProjectParallelWorkerOptions } from './InteropInterfaces.js'
|
|
9
10
|
|
|
10
11
|
const numCPUs = cpus().length;
|
|
@@ -74,9 +75,13 @@ export class ProjectParallelRunner extends BaseRunner {
|
|
|
74
75
|
constructor(project: HttpProject, opts: IProjectParallelRunnerOptions = {}) {
|
|
75
76
|
super();
|
|
76
77
|
this.project = project;
|
|
77
|
-
this.options = opts
|
|
78
|
+
this.options = opts;
|
|
78
79
|
|
|
79
80
|
this._exitHandler = this._exitHandler.bind(this);
|
|
81
|
+
this._abortHandler = this._abortHandler.bind(this);
|
|
82
|
+
if (opts.signal) {
|
|
83
|
+
this.signal = opts.signal;
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
execute(): Promise<IProjectExecutionLog> {
|
|
@@ -87,7 +92,70 @@ export class ProjectParallelRunner extends BaseRunner {
|
|
|
87
92
|
});
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
protected _state: State = State.Idle;
|
|
96
|
+
|
|
97
|
+
get state(): State {
|
|
98
|
+
return this._state;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected _signal?: AbortSignal;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The abort signal to set on this request.
|
|
105
|
+
* Aborts the request when the signal fires.
|
|
106
|
+
* @type {(AbortSignal | undefined)}
|
|
107
|
+
*/
|
|
108
|
+
get signal(): AbortSignal | undefined {
|
|
109
|
+
return this._signal;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
set signal(value: AbortSignal | undefined) {
|
|
113
|
+
const old = this._signal;
|
|
114
|
+
if (old === value) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this._signal = value;
|
|
118
|
+
if (old) {
|
|
119
|
+
old.removeEventListener('abort', this._abortHandler);
|
|
120
|
+
}
|
|
121
|
+
if (value) {
|
|
122
|
+
value.addEventListener('abort', this._abortHandler);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Aborts the current run.
|
|
128
|
+
* The promise returned by the `execute()` method will reject if not yet resolved.
|
|
129
|
+
*/
|
|
130
|
+
abort(): void {
|
|
131
|
+
this._state = State.Aborted;
|
|
132
|
+
const { workers } = this;
|
|
133
|
+
workers.forEach((info) => {
|
|
134
|
+
if (info.status !== 'error') {
|
|
135
|
+
try {
|
|
136
|
+
info.worker.destroy();
|
|
137
|
+
} catch (e) {
|
|
138
|
+
// ...
|
|
139
|
+
}
|
|
140
|
+
info.status = 'error';
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
if (this.mainRejecter) {
|
|
144
|
+
this.mainRejecter!(new Error(`The execution has been aborted.`));
|
|
145
|
+
this.mainRejecter = undefined;
|
|
146
|
+
this.mainResolver = undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handler for the `abort` event on the `AbortSignal`.
|
|
152
|
+
*/
|
|
153
|
+
protected _abortHandler(): void {
|
|
154
|
+
this.abort();
|
|
155
|
+
}
|
|
156
|
+
|
|
90
157
|
private _execute(): void {
|
|
158
|
+
this._state = State.Running as State;
|
|
91
159
|
try {
|
|
92
160
|
cluster.setupPrimary({
|
|
93
161
|
exec: join(__dirname, 'ProjectRunnerWorker.js'),
|
|
@@ -105,6 +173,9 @@ export class ProjectParallelRunner extends BaseRunner {
|
|
|
105
173
|
} catch (e) {
|
|
106
174
|
const cause = e as Error;
|
|
107
175
|
this.mainRejecter!(cause);
|
|
176
|
+
this.mainResolver = undefined
|
|
177
|
+
this.mainRejecter = undefined
|
|
178
|
+
this._state = State.Idle as State;
|
|
108
179
|
}
|
|
109
180
|
}
|
|
110
181
|
|
|
@@ -211,7 +282,10 @@ export class ProjectParallelRunner extends BaseRunner {
|
|
|
211
282
|
this.endTime = Date.now();
|
|
212
283
|
const report = await this.createReport();
|
|
213
284
|
this.mainResolver(report);
|
|
285
|
+
this.mainResolver = undefined
|
|
286
|
+
this.mainRejecter = undefined
|
|
214
287
|
cluster.off('exit', this._exitHandler);
|
|
288
|
+
this._state = State.Idle as State;
|
|
215
289
|
}
|
|
216
290
|
|
|
217
291
|
private setRunError(worker: Worker, message: IWorkerMessage): void {
|
|
@@ -14,6 +14,7 @@ import { VariablesProcessor } from '../variables/VariablesProcessor.js';
|
|
|
14
14
|
import { RequestFactory } from './RequestFactory.js';
|
|
15
15
|
import { EventTypes } from '../../events/EventTypes.js';
|
|
16
16
|
import { ProjectRunnerOptions, ProjectRunnerRunOptions, RunResult } from './InteropInterfaces.js';
|
|
17
|
+
import { State } from './enums.js';
|
|
17
18
|
|
|
18
19
|
export interface ProjectRequestRunner {
|
|
19
20
|
/**
|
|
@@ -62,6 +63,12 @@ export class ProjectRequestRunner extends EventEmitter {
|
|
|
62
63
|
*/
|
|
63
64
|
protected variablesProcessor = new VariablesProcessor();
|
|
64
65
|
|
|
66
|
+
protected _state: State = State.Idle;
|
|
67
|
+
|
|
68
|
+
get state(): State {
|
|
69
|
+
return this._state;
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
constructor(project: HttpProject, opts: ProjectRunnerOptions = {}) {
|
|
66
73
|
super();
|
|
67
74
|
this.project = project;
|
|
@@ -77,15 +84,25 @@ export class ProjectRequestRunner extends EventEmitter {
|
|
|
77
84
|
* @returns A promise with the run result.
|
|
78
85
|
*/
|
|
79
86
|
async run(options?: ProjectRunnerRunOptions): Promise<RunResult[]> {
|
|
87
|
+
this._state = State.Running as State;
|
|
80
88
|
const { project } = this;
|
|
81
89
|
const executed: RunResult[] = [];
|
|
82
90
|
for (const request of project.requestIterator(options)) {
|
|
83
91
|
const info = await this._runItem(request);
|
|
84
92
|
executed.push(info);
|
|
85
93
|
}
|
|
94
|
+
this._state = State.Idle;
|
|
86
95
|
return executed;
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Aborts the current run.
|
|
100
|
+
* The promise returned by the `run()` method will reject if not yet resolved.
|
|
101
|
+
*/
|
|
102
|
+
abort(): void {
|
|
103
|
+
this._state = State.Aborted;
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
/**
|
|
90
107
|
* Allows to iterate over project requests recursively and execute each request
|
|
91
108
|
* in order. The generator yields the `RunResult` for the request.
|
|
@@ -101,13 +118,18 @@ export class ProjectRequestRunner extends EventEmitter {
|
|
|
101
118
|
*/
|
|
102
119
|
async* [Symbol.asyncIterator](): AsyncGenerator<RunResult> {
|
|
103
120
|
const { project } = this;
|
|
121
|
+
this._state = State.Running as State;
|
|
104
122
|
for (const request of project.requestIterator({ recursive: true })) {
|
|
105
123
|
const info = await this._runItem(request);
|
|
106
124
|
yield info;
|
|
107
125
|
}
|
|
126
|
+
this._state = State.Idle;
|
|
108
127
|
}
|
|
109
128
|
|
|
110
129
|
private async _runItem(request: ProjectRequest): Promise<RunResult> {
|
|
130
|
+
if (this._state === State.Aborted) {
|
|
131
|
+
throw new Error(`The execution has been aborted.`);
|
|
132
|
+
}
|
|
111
133
|
const folder = request.getParent();
|
|
112
134
|
const parent = folder || this.project;
|
|
113
135
|
let variables: Record<string, string>;
|