@forge/events 0.0.0-experimental-d18f8dd → 0.0.0-experimental-416047f
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 +94 -4
- package/out/__test__/invocationError.test.d.ts +2 -0
- package/out/__test__/invocationError.test.d.ts.map +1 -0
- package/out/__test__/invocationError.test.js +21 -0
- package/out/__test__/jobProgress.test.js +25 -1
- package/out/__test__/queue.test.js +5 -5
- package/out/__test__/retryOptions.test.d.ts +2 -0
- package/out/__test__/retryOptions.test.d.ts.map +1 -0
- package/out/__test__/retryOptions.test.js +14 -0
- package/out/errors.d.ts +1 -1
- package/out/errors.d.ts.map +1 -1
- package/out/errors.js +2 -2
- package/out/index.d.ts +3 -0
- package/out/index.d.ts.map +1 -1
- package/out/index.js +6 -0
- package/out/invocationError.d.ts +7 -0
- package/out/invocationError.d.ts.map +1 -0
- package/out/invocationError.js +12 -0
- package/out/invocationErrorCode.d.ts +9 -0
- package/out/invocationErrorCode.d.ts.map +1 -0
- package/out/invocationErrorCode.js +12 -0
- package/out/jobProgress.d.ts +1 -0
- package/out/jobProgress.d.ts.map +1 -1
- package/out/jobProgress.js +12 -0
- package/out/queries.d.ts +1 -0
- package/out/queries.d.ts.map +1 -1
- package/out/queries.js +2 -1
- package/out/queueResponse.d.ts +5 -1
- package/out/queueResponse.d.ts.map +1 -1
- package/out/queueResponse.js +9 -3
- package/out/retryOptions.d.ts +9 -0
- package/out/retryOptions.d.ts.map +1 -0
- package/out/retryOptions.js +20 -0
- package/out/text.d.ts +1 -0
- package/out/text.d.ts.map +1 -1
- package/out/text.js +3 -2
- package/out/types.d.ts +1 -0
- package/out/types.d.ts.map +1 -1
- package/out/validators.d.ts +3 -1
- package/out/validators.d.ts.map +1 -1
- package/out/validators.js +15 -3
- package/package.json +5 -5
- package/src/__test__/invocationError.test.ts +23 -0
- package/src/__test__/jobProgress.test.ts +32 -1
- package/src/__test__/queue.test.ts +8 -6
- package/src/__test__/retryOptions.test.ts +14 -0
- package/src/errors.ts +2 -2
- package/src/index.ts +3 -0
- package/src/invocationError.ts +8 -0
- package/src/invocationErrorCode.ts +8 -0
- package/src/jobProgress.ts +23 -3
- package/src/queries.ts +1 -0
- package/src/queueResponse.ts +8 -2
- package/src/retryOptions.ts +26 -0
- package/src/text.ts +3 -2
- package/src/types.ts +1 -0
- package/src/validators.ts +18 -3
- package/tsconfig.tsbuildinfo +253 -231
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RetryOptions } from '../retryOptions';
|
|
2
|
+
import { InvocationError } from '../invocationError';
|
|
3
|
+
import { InvocationErrorCode } from '../invocationErrorCode';
|
|
4
|
+
|
|
5
|
+
describe('InvocationError tests', () => {
|
|
6
|
+
const defaultRetryOption = new RetryOptions();
|
|
7
|
+
let target = new InvocationError();
|
|
8
|
+
|
|
9
|
+
it('Populate invocationError with default retryOptions and expect default retry option', async () => {
|
|
10
|
+
expect(target.retryOptions.retryAfter).toEqual(defaultRetryOption.retryAfter);
|
|
11
|
+
expect(target.retryOptions.retryReason).toEqual(InvocationErrorCode.FUNCTION_RETRY_REQUEST);
|
|
12
|
+
expect((target as any).hasOwnProperty('_retry')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('Populate InvocationError with custom RetryOptions and expect the custom value to set.', () => {
|
|
16
|
+
defaultRetryOption.retryAfter = 10;
|
|
17
|
+
defaultRetryOption.retryReason = InvocationErrorCode.FUNCTION_OUT_OF_MEMORY;
|
|
18
|
+
target = new InvocationError(defaultRetryOption);
|
|
19
|
+
|
|
20
|
+
expect(target.retryOptions).toEqual(defaultRetryOption);
|
|
21
|
+
expect((target as any).hasOwnProperty('_retry')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -37,7 +37,7 @@ describe('JobProgress methods', () => {
|
|
|
37
37
|
);
|
|
38
38
|
const jobProgress = getJobProgress(apiClientMock, 'test-queue-name#test-job-id');
|
|
39
39
|
await expect(jobProgress.getStats()).rejects.toThrow(
|
|
40
|
-
new JobDoesNotExistError(`The job test-job-id
|
|
40
|
+
new JobDoesNotExistError(`The job test-job-id was not found for the queue test-queue-name.`)
|
|
41
41
|
);
|
|
42
42
|
verifyApiClientCalledWith(apiClientMock, '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}', {
|
|
43
43
|
queueName: 'test-queue-name',
|
|
@@ -75,6 +75,37 @@ describe('JobProgress methods', () => {
|
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
describe('cancel', () => {
|
|
79
|
+
it('should call the queue/cancel endpoint', async () => {
|
|
80
|
+
const apiClientMock = getApiClientMock({}, 204);
|
|
81
|
+
const jobProgress = getJobProgress(apiClientMock, 'test-queue-name#test-job-id');
|
|
82
|
+
const response = await jobProgress.cancel();
|
|
83
|
+
verifyApiClientCalledWith(apiClientMock, '/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}', {
|
|
84
|
+
queueName: 'test-queue-name',
|
|
85
|
+
jobId: 'test-job-id'
|
|
86
|
+
});
|
|
87
|
+
expect(response.status).toEqual(204);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should throw JobDoesNotExistError', async () => {
|
|
91
|
+
const apiClientMock = getApiClientMock(
|
|
92
|
+
{
|
|
93
|
+
message: 'Job Not Found',
|
|
94
|
+
code: 404
|
|
95
|
+
},
|
|
96
|
+
404
|
|
97
|
+
);
|
|
98
|
+
const jobProgress = getJobProgress(apiClientMock, 'test-queue-name#test-job-id');
|
|
99
|
+
await expect(jobProgress.cancel()).rejects.toThrow(
|
|
100
|
+
new JobDoesNotExistError(`The job test-job-id was not found for the queue test-queue-name.`)
|
|
101
|
+
);
|
|
102
|
+
verifyApiClientCalledWith(apiClientMock, '/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}', {
|
|
103
|
+
queueName: 'test-queue-name',
|
|
104
|
+
jobId: 'test-job-id'
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
78
109
|
it('should throw InternalServerError when WHP returns 422 response', async () => {
|
|
79
110
|
const apiClientMock = getApiClientMock(
|
|
80
111
|
{
|
|
@@ -55,8 +55,8 @@ describe('Queue methods', () => {
|
|
|
55
55
|
it('should throw TooManyEventsError', async () => {
|
|
56
56
|
const apiClientMock = getApiClientMock();
|
|
57
57
|
const queue = getQueue(apiClientMock, 'name');
|
|
58
|
-
await expect(queue.push([
|
|
59
|
-
new TooManyEventsError(`
|
|
58
|
+
await expect(queue.push([...Array(51).keys()])).rejects.toThrow(
|
|
59
|
+
new TooManyEventsError(`This push contains more than the 50 events allowed.`)
|
|
60
60
|
);
|
|
61
61
|
expect(apiClientMock).toHaveBeenCalledTimes(0);
|
|
62
62
|
});
|
|
@@ -73,7 +73,7 @@ describe('Queue methods', () => {
|
|
|
73
73
|
it('should throw RateLimitError', async () => {
|
|
74
74
|
const apiClientMock = getApiClientMock({}, 429);
|
|
75
75
|
const queue = getQueue(apiClientMock, 'name');
|
|
76
|
-
const payload = [
|
|
76
|
+
const payload = [...Array(10).keys()];
|
|
77
77
|
await expect(queue.push(payload)).rejects.toThrow(new RateLimitError(`Too many requests.`));
|
|
78
78
|
verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
|
|
79
79
|
});
|
|
@@ -81,8 +81,10 @@ describe('Queue methods', () => {
|
|
|
81
81
|
it('should throw InvocationLimitReachedError', async () => {
|
|
82
82
|
const apiClientMock = getApiClientMock({}, 405);
|
|
83
83
|
const queue = getQueue(apiClientMock, 'name');
|
|
84
|
-
const payload = [
|
|
85
|
-
await expect(queue.push(payload)).rejects.toThrow(
|
|
84
|
+
const payload = [...Array(5).keys()];
|
|
85
|
+
await expect(queue.push(payload)).rejects.toThrow(
|
|
86
|
+
new InvocationLimitReachedError(`The limit on cyclic invocation has been reached.`)
|
|
87
|
+
);
|
|
86
88
|
verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
|
|
87
89
|
});
|
|
88
90
|
|
|
@@ -203,7 +205,7 @@ describe('Queue methods', () => {
|
|
|
203
205
|
500
|
|
204
206
|
);
|
|
205
207
|
const queue = getQueue(apiClientMock, 'name');
|
|
206
|
-
const payload = [
|
|
208
|
+
const payload = [...Array(9).keys()];
|
|
207
209
|
await expect(queue.push(payload)).rejects.toThrow(
|
|
208
210
|
new InternalServerError(
|
|
209
211
|
`500 Status Text: AWS SQS timed out`,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RetryOptions } from '../retryOptions';
|
|
2
|
+
|
|
3
|
+
describe('RetryOptions tests', () => {
|
|
4
|
+
const target = new RetryOptions();
|
|
5
|
+
|
|
6
|
+
it.each([
|
|
7
|
+
[RetryOptions.MIN_RETRY_AFTER, RetryOptions.MIN_RETRY_AFTER],
|
|
8
|
+
[RetryOptions.MIN_RETRY_AFTER - 1, RetryOptions.MIN_RETRY_AFTER],
|
|
9
|
+
[RetryOptions.MIN_RETRY_AFTER + 1, RetryOptions.MIN_RETRY_AFTER + 1]
|
|
10
|
+
])('try to set retryAfter to %p, expect retryAfter to be %p', (value: number, result: number) => {
|
|
11
|
+
target.retryAfter = value;
|
|
12
|
+
expect(target.retryAfter).toEqual(result);
|
|
13
|
+
});
|
|
14
|
+
});
|
package/src/errors.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -13,3 +13,6 @@ export {
|
|
|
13
13
|
} from './errors';
|
|
14
14
|
export { JobProgress } from './jobProgress';
|
|
15
15
|
export { QueueResponse } from './queueResponse';
|
|
16
|
+
export { InvocationError } from './invocationError';
|
|
17
|
+
export { InvocationErrorCode } from './invocationErrorCode';
|
|
18
|
+
export { RetryOptions } from './retryOptions';
|
package/src/jobProgress.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { GET_STATS_PATH, post } from './queries';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { CANCEL_JOB_PATH, GET_STATS_PATH, post } from './queries';
|
|
2
|
+
import {
|
|
3
|
+
validateCancelJobAPIResponse,
|
|
4
|
+
validateCancelJobRequest,
|
|
5
|
+
validateGetStatsAPIResponse,
|
|
6
|
+
validateGetStatsPayload
|
|
7
|
+
} from './validators';
|
|
8
|
+
import { APIResponse, CancelJobRequest, FetchMethod, GetStatsRequest } from './types';
|
|
4
9
|
|
|
5
10
|
export class JobProgress {
|
|
6
11
|
private readonly apiClient: FetchMethod;
|
|
@@ -25,4 +30,19 @@ export class JobProgress {
|
|
|
25
30
|
await validateGetStatsAPIResponse(response, getStatsRequest);
|
|
26
31
|
return response;
|
|
27
32
|
}
|
|
33
|
+
|
|
34
|
+
async cancel(): Promise<APIResponse> {
|
|
35
|
+
const [queueName, jobId] = this.id.split('#');
|
|
36
|
+
const cancelJobRequest: CancelJobRequest = {
|
|
37
|
+
queueName: queueName,
|
|
38
|
+
jobId: jobId,
|
|
39
|
+
time: new Date().toISOString()
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
validateCancelJobRequest(cancelJobRequest);
|
|
43
|
+
|
|
44
|
+
const response = await post(CANCEL_JOB_PATH, cancelJobRequest, this.apiClient);
|
|
45
|
+
await validateCancelJobAPIResponse(response, cancelJobRequest);
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
28
48
|
}
|
package/src/queries.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { APIRequest, APIResponse, FetchMethod } from './types';
|
|
|
2
2
|
|
|
3
3
|
export const PUSH_PATH = '/webhook/queue/publish/{cloudId}/{environmentId}/{appId}/{appVersion}';
|
|
4
4
|
export const GET_STATS_PATH = '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}';
|
|
5
|
+
export const CANCEL_JOB_PATH = '/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}';
|
|
5
6
|
|
|
6
7
|
export const post = async (endpoint: string, body: APIRequest, apiClient: FetchMethod): Promise<APIResponse> => {
|
|
7
8
|
const request = {
|
package/src/queueResponse.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
export class
|
|
2
|
-
protected _retry
|
|
1
|
+
export class Response {
|
|
2
|
+
constructor(protected _retry: boolean) {}
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export class QueueResponse extends Response {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(false);
|
|
8
|
+
}
|
|
3
9
|
|
|
4
10
|
retry(): void {
|
|
5
11
|
this._retry = true;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { InvocationErrorCode } from './invocationErrorCode';
|
|
2
|
+
|
|
3
|
+
export class RetryOptions {
|
|
4
|
+
static readonly MIN_RETRY_AFTER = 1;
|
|
5
|
+
|
|
6
|
+
private _retryAfter = RetryOptions.MIN_RETRY_AFTER;
|
|
7
|
+
|
|
8
|
+
public retryReason: InvocationErrorCode = InvocationErrorCode.FUNCTION_RETRY_REQUEST;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sets the retryAfter option to indicate seconds to wait before attempting a retry.
|
|
12
|
+
* @param valueInSeconds new value in seconds
|
|
13
|
+
*/
|
|
14
|
+
set retryAfter(valueInSeconds: number) {
|
|
15
|
+
if (valueInSeconds >= RetryOptions.MIN_RETRY_AFTER) {
|
|
16
|
+
this._retryAfter = valueInSeconds;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns value of retryAfter to indicate seconds to wait before attempting a retry.
|
|
22
|
+
*/
|
|
23
|
+
get retryAfter(): number {
|
|
24
|
+
return this._retryAfter;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/text.ts
CHANGED
|
@@ -3,12 +3,13 @@ export const Text = {
|
|
|
3
3
|
invalidQueueName: `Queue names can only contain alphanumeric characters, dashes and underscores.`,
|
|
4
4
|
invalidDelayInSecondsSetting: `The delayInSeconds setting must be between 0 and 900.`,
|
|
5
5
|
maxEventsAllowed: (maxEventsCount: number): string =>
|
|
6
|
-
`
|
|
6
|
+
`This push contains more than the ${maxEventsCount} events allowed.`,
|
|
7
7
|
maxPayloadAllowed: (maxPayloadSize: number): string => `The maximum payload size is ${maxPayloadSize}KB.`,
|
|
8
8
|
noEventsPushed: `No events pushed.`,
|
|
9
9
|
rateLimitError: `Too many requests.`,
|
|
10
|
+
invocationLimitReachedError: `The limit on cyclic invocation has been reached.`,
|
|
10
11
|
jobIdEmpty: `jobId cannot be empty.`,
|
|
11
12
|
jobDoesNotExit: (jobId: string, queueName: string): string =>
|
|
12
|
-
`The job ${jobId}
|
|
13
|
+
`The job ${jobId} was not found for the queue ${queueName}.`
|
|
13
14
|
}
|
|
14
15
|
};
|
package/src/types.ts
CHANGED
package/src/validators.ts
CHANGED
|
@@ -11,10 +11,10 @@ import {
|
|
|
11
11
|
InvocationLimitReachedError
|
|
12
12
|
} from './errors';
|
|
13
13
|
import { Text } from './text';
|
|
14
|
-
import { APIResponse, GetStatsRequest, Payload, PushRequest, PushSettings } from './types';
|
|
14
|
+
import { APIResponse, CancelJobRequest, GetStatsRequest, Payload, PushRequest, PushSettings } from './types';
|
|
15
15
|
|
|
16
16
|
const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
17
|
-
const MAXIMUM_EVENTS =
|
|
17
|
+
const MAXIMUM_EVENTS = 50;
|
|
18
18
|
const MAXIMUM_PAYLOAD_SIZE_KB = 200;
|
|
19
19
|
|
|
20
20
|
export const validateQueueKey = (queueName: string) => {
|
|
@@ -51,13 +51,20 @@ export const validateGetStatsPayload = (getStatsRequest: GetStatsRequest) => {
|
|
|
51
51
|
validateQueueKey(getStatsRequest.queueName);
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
export const validateCancelJobRequest = (cancelJobRequest: CancelJobRequest) => {
|
|
55
|
+
if (!cancelJobRequest.jobId) {
|
|
56
|
+
throw new JobDoesNotExistError(Text.error.jobIdEmpty);
|
|
57
|
+
}
|
|
58
|
+
validateQueueKey(cancelJobRequest.queueName);
|
|
59
|
+
};
|
|
60
|
+
|
|
54
61
|
export const validateAPIResponse = async (response: APIResponse, expectedSuccessStatus: number) => {
|
|
55
62
|
if (response.status === 429) {
|
|
56
63
|
throw new RateLimitError(Text.error.rateLimitError);
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
if (response.status === 405) {
|
|
60
|
-
throw new InvocationLimitReachedError();
|
|
67
|
+
throw new InvocationLimitReachedError(Text.error.invocationLimitReachedError);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
if (response.status != expectedSuccessStatus && response.status) {
|
|
@@ -125,3 +132,11 @@ export const validateGetStatsAPIResponse = async (response: APIResponse, getStat
|
|
|
125
132
|
|
|
126
133
|
await validateAPIResponse(response, 200);
|
|
127
134
|
};
|
|
135
|
+
|
|
136
|
+
export const validateCancelJobAPIResponse = async (response: APIResponse, cancelJobRequest: GetStatsRequest) => {
|
|
137
|
+
if (response.status === 404) {
|
|
138
|
+
throw new JobDoesNotExistError(Text.error.jobDoesNotExit(cancelJobRequest.jobId, cancelJobRequest.queueName));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await validateAPIResponse(response, 204);
|
|
142
|
+
};
|