@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.
Files changed (74) hide show
  1. package/build/src/events/BaseEvents.d.ts +1 -1
  2. package/build/src/events/BaseEvents.js.map +1 -1
  3. package/build/src/events/EventTypes.d.ts +16 -0
  4. package/build/src/events/EventTypes.js +2 -0
  5. package/build/src/events/EventTypes.js.map +1 -1
  6. package/build/src/events/Events.d.ts +16 -0
  7. package/build/src/events/Events.js +2 -0
  8. package/build/src/events/Events.js.map +1 -1
  9. package/build/src/events/transport/TransportEventTypes.d.ts +31 -0
  10. package/build/src/events/transport/TransportEventTypes.js +36 -0
  11. package/build/src/events/transport/TransportEventTypes.js.map +1 -0
  12. package/build/src/events/transport/TransportEvents.d.ts +77 -0
  13. package/build/src/events/transport/TransportEvents.js +91 -0
  14. package/build/src/events/transport/TransportEvents.js.map +1 -0
  15. package/build/src/lib/transformers/PayloadSerializer.d.ts +11 -5
  16. package/build/src/lib/transformers/PayloadSerializer.js +36 -10
  17. package/build/src/lib/transformers/PayloadSerializer.js.map +1 -1
  18. package/build/src/mocking/ProjectMock.d.ts +2 -0
  19. package/build/src/mocking/ProjectMock.js +3 -0
  20. package/build/src/mocking/ProjectMock.js.map +1 -1
  21. package/build/src/mocking/lib/Url.d.ts +12 -0
  22. package/build/src/mocking/lib/Url.js +31 -0
  23. package/build/src/mocking/lib/Url.js.map +1 -0
  24. package/build/src/models/RequestConfig.d.ts +10 -0
  25. package/build/src/models/RequestConfig.js +13 -1
  26. package/build/src/models/RequestConfig.js.map +1 -1
  27. package/build/src/models/SerializablePayload.js +1 -6
  28. package/build/src/models/SerializablePayload.js.map +1 -1
  29. package/build/src/runtime/http-engine/CoreEngine.js +1 -1
  30. package/build/src/runtime/http-engine/CoreEngine.js.map +1 -1
  31. package/build/src/runtime/http-engine/HttpEngine.d.ts +12 -0
  32. package/build/src/runtime/http-engine/HttpEngine.js +34 -0
  33. package/build/src/runtime/http-engine/HttpEngine.js.map +1 -1
  34. package/build/src/runtime/node/InteropInterfaces.d.ts +5 -0
  35. package/build/src/runtime/node/ProjectParallelRunner.d.ts +20 -0
  36. package/build/src/runtime/node/ProjectParallelRunner.js +69 -1
  37. package/build/src/runtime/node/ProjectParallelRunner.js.map +1 -1
  38. package/build/src/runtime/node/ProjectRequestRunner.d.ts +8 -0
  39. package/build/src/runtime/node/ProjectRequestRunner.js +19 -0
  40. package/build/src/runtime/node/ProjectRequestRunner.js.map +1 -1
  41. package/build/src/runtime/node/ProjectRunner.d.ts +25 -0
  42. package/build/src/runtime/node/ProjectRunner.js +55 -0
  43. package/build/src/runtime/node/ProjectRunner.js.map +1 -1
  44. package/build/src/runtime/node/ProjectRunnerWorker.js +3 -0
  45. package/build/src/runtime/node/ProjectRunnerWorker.js.map +1 -1
  46. package/build/src/runtime/node/ProjectSerialRunner.js +3 -0
  47. package/build/src/runtime/node/ProjectSerialRunner.js.map +1 -1
  48. package/build/src/runtime/node/RequestFactory.d.ts +6 -0
  49. package/build/src/runtime/node/RequestFactory.js +10 -1
  50. package/build/src/runtime/node/RequestFactory.js.map +1 -1
  51. package/build/src/runtime/node/enums.d.ts +8 -0
  52. package/build/src/runtime/node/enums.js +10 -0
  53. package/build/src/runtime/node/enums.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/events/BaseEvents.ts +1 -1
  56. package/src/events/EventTypes.ts +2 -0
  57. package/src/events/Events.ts +2 -0
  58. package/src/events/transport/TransportEventTypes.ts +37 -0
  59. package/src/events/transport/TransportEvents.ts +116 -0
  60. package/src/lib/transformers/PayloadSerializer.ts +38 -11
  61. package/src/mocking/ProjectMock.ts +3 -0
  62. package/src/mocking/lib/Url.ts +35 -0
  63. package/src/models/RequestConfig.ts +19 -1
  64. package/src/models/SerializablePayload.ts +1 -5
  65. package/src/runtime/http-engine/CoreEngine.ts +1 -1
  66. package/src/runtime/http-engine/HttpEngine.ts +39 -0
  67. package/src/runtime/node/InteropInterfaces.ts +6 -0
  68. package/src/runtime/node/ProjectParallelRunner.ts +75 -1
  69. package/src/runtime/node/ProjectRequestRunner.ts +22 -0
  70. package/src/runtime/node/ProjectRunner.ts +63 -0
  71. package/src/runtime/node/ProjectRunnerWorker.ts +3 -0
  72. package/src/runtime/node/ProjectSerialRunner.ts +3 -2
  73. package/src/runtime/node/RequestFactory.ts +11 -1
  74. 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
- * Transforms the payload into a data store safe object.
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 async serialize(payload: DeserializedPayload): Promise<ISafePayload | string | undefined> {
70
- if (typeof payload === 'undefined' || payload === null) {
71
- return undefined;
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 payload;
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 {any} payload
133
- * @returns {TransformedPayload|undefined} The buffer metadata or undefined if the passed argument is not an ArrayBuffer.
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 {string} name The part name
168
- * @param {string|File} file The part value
169
- * @return {Promise<IMultipartBody>} Transformed FormData part to a datastore safe entry.
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 (typeof payload === 'undefined' || payload === null) {
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
- if (typeof message === 'string') {
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
  /**
@@ -74,7 +74,7 @@ export class CoreEngine extends HttpEngine {
74
74
  } else {
75
75
  await this.connect();
76
76
  }
77
- if (!this.socket) {
77
+ if (!this.socket || this.aborted) {
78
78
  return;
79
79
  }
80
80
  const message = await this.prepareMessage();
@@ -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>;