@forge/events 1.1.0-next.0 → 2.0.0-next.1

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/out/text.js CHANGED
@@ -4,6 +4,8 @@ exports.Text = void 0;
4
4
  exports.Text = {
5
5
  error: {
6
6
  invalidQueueName: `Queue names can only contain alphanumeric characters, dashes and underscores.`,
7
+ invalidEvent: `Event must be an object.`,
8
+ invalidEventBody: `Event body must be an object.`,
7
9
  invalidDelayInSecondsSetting: `The delayInSeconds setting must be between 0 and 900.`,
8
10
  maxEventsAllowed: (maxEventsCount) => `This push contains more than the ${maxEventsCount} events allowed.`,
9
11
  maxPayloadAllowed: (maxPayloadSize) => `The maximum payload size is ${maxPayloadSize}KB.`,
package/out/types.d.ts CHANGED
@@ -1,26 +1,41 @@
1
- export declare type Payload = string | number | boolean | {
2
- [key: string]: Payload;
3
- };
4
- export interface PushSettings {
5
- delayInSeconds: number;
6
- }
1
+ import { InvocationErrorCode } from './invocationErrorCode';
2
+ export declare type Payload = Record<string, unknown>;
7
3
  export interface QueueParams {
8
4
  key: string;
9
5
  }
10
6
  export interface PushRequest extends APIRequest {
11
- payload: Payload[];
7
+ payload: PushEvent[];
12
8
  schema: string;
13
9
  type: string;
14
- delayInSeconds?: number;
10
+ jobId: string;
15
11
  }
16
12
  export interface APIRequest {
17
13
  queueName: string;
18
14
  jobId: string;
19
15
  time: string;
20
16
  }
17
+ export interface PushEvent {
18
+ body: Body;
19
+ delayInSeconds?: number;
20
+ }
21
+ export declare type Body = Record<string, unknown>;
22
+ export interface PushResult {
23
+ jobId: string;
24
+ }
21
25
  export interface FailedEvent {
22
26
  errorMessage: string;
23
- payload: Payload;
27
+ event: PushEvent;
28
+ }
29
+ export interface AsyncEvent extends PushEvent {
30
+ queueName: string;
31
+ jobId: string;
32
+ eventId: string;
33
+ retryContext?: RetryContext;
34
+ }
35
+ export interface RetryContext {
36
+ retryCount: number;
37
+ retryReason: InvocationErrorCode;
38
+ retryData: any;
24
39
  }
25
40
  export declare type GetStatsRequest = APIRequest;
26
41
  export declare type CancelJobRequest = APIRequest;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oBAAY,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAE7E,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AACD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,oBAAY,eAAe,GAAG,UAAU,CAAC;AACzC,oBAAY,gBAAgB,GAAG,UAAU,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,oBAAY,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oBAAY,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,mBAAmB,CAAC;IACjC,SAAS,EAAE,GAAG,CAAC;CAChB;AAED,oBAAY,eAAe,GAAG,UAAU,CAAC;AACzC,oBAAY,gBAAgB,GAAG,UAAU,CAAC"}
@@ -1,12 +1,12 @@
1
1
  import { APIResponse } from '@forge/api';
2
- import { CancelJobRequest, GetStatsRequest, Payload, PushRequest, PushSettings } from './types';
2
+ import { CancelJobRequest, GetStatsRequest, PushEvent, PushRequest, PushResult } from './types';
3
3
  export declare const validateQueueKey: (queueName: string) => void;
4
- export declare const validatePushSettings: (settings: PushSettings) => void;
5
- export declare const validatePushPayloads: (payloads: Payload | Payload[]) => void;
4
+ export declare const validatePushSettings: (event: PushEvent) => void;
5
+ export declare function validatePushEvents(arg: PushEvent | PushEvent[]): PushEvent[];
6
6
  export declare const validateGetStatsPayload: (getStatsRequest: GetStatsRequest) => void;
7
7
  export declare const validateCancelJobRequest: (cancelJobRequest: CancelJobRequest) => void;
8
8
  export declare const validateAPIResponse: (response: APIResponse, expectedSuccessStatus: number) => Promise<void>;
9
- export declare const validatePushAPIResponse: (response: APIResponse, requestBody: PushRequest) => Promise<void>;
9
+ export declare const validatePushAPIResponse: (requestBody: PushRequest, response: APIResponse, result: PushResult) => Promise<void>;
10
10
  export declare const validateGetStatsAPIResponse: (response: APIResponse, getStatsRequest: GetStatsRequest) => Promise<void>;
11
11
  export declare const validateCancelJobAPIResponse: (response: APIResponse, cancelJobRequest: GetStatsRequest) => Promise<void>;
12
12
  //# sourceMappingURL=validators.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAczC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMhG,eAAO,MAAM,gBAAgB,cAAe,MAAM,SAIjD,CAAC;AAEF,eAAO,MAAM,oBAAoB,aAAc,YAAY,SAI1D,CAAC;AAEF,eAAO,MAAM,oBAAoB,aAAc,OAAO,GAAG,OAAO,EAAE,SAajE,CAAC;AAEF,eAAO,MAAM,uBAAuB,oBAAqB,eAAe,SAKvE,CAAC;AAEF,eAAO,MAAM,wBAAwB,qBAAsB,gBAAgB,SAK1E,CAAC;AAEF,eAAO,MAAM,mBAAmB,aAAoB,WAAW,yBAAyB,MAAM,kBA6B7F,CAAC;AAEF,eAAO,MAAM,uBAAuB,aAAoB,WAAW,eAAe,WAAW,kBAkC5F,CAAC;AAEF,eAAO,MAAM,2BAA2B,aAAoB,WAAW,mBAAmB,eAAe,kBAMxG,CAAC;AAEF,eAAO,MAAM,4BAA4B,aAAoB,WAAW,oBAAoB,eAAe,kBAM1G,CAAC"}
1
+ {"version":3,"file":"validators.d.ts","sourceRoot":"","sources":["../src/validators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAezC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAMhG,eAAO,MAAM,gBAAgB,cAAe,MAAM,SAIjD,CAAC;AAEF,eAAO,MAAM,oBAAoB,UAAW,SAAS,SAIpD,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,EAAE,GAAG,SAAS,EAAE,CA6B5E;AAED,eAAO,MAAM,uBAAuB,oBAAqB,eAAe,SAKvE,CAAC;AAEF,eAAO,MAAM,wBAAwB,qBAAsB,gBAAgB,SAK1E,CAAC;AAEF,eAAO,MAAM,mBAAmB,aAAoB,WAAW,yBAAyB,MAAM,kBA6B7F,CAAC;AAEF,eAAO,MAAM,uBAAuB,gBAAuB,WAAW,YAAY,WAAW,UAAU,UAAU,kBAkChH,CAAC;AAEF,eAAO,MAAM,2BAA2B,aAAoB,WAAW,mBAAmB,eAAe,kBAMxG,CAAC;AAEF,eAAO,MAAM,4BAA4B,aAAoB,WAAW,oBAAoB,eAAe,kBAM1G,CAAC"}
package/out/validators.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateCancelJobAPIResponse = exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validateCancelJobRequest = exports.validateGetStatsPayload = exports.validatePushPayloads = exports.validatePushSettings = exports.validateQueueKey = void 0;
3
+ exports.validateCancelJobAPIResponse = exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validateCancelJobRequest = exports.validateGetStatsPayload = exports.validatePushEvents = exports.validatePushSettings = exports.validateQueueKey = void 0;
4
4
  const errors_1 = require("./errors");
5
5
  const text_1 = require("./text");
6
6
  const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
@@ -12,25 +12,36 @@ const validateQueueKey = (queueName) => {
12
12
  }
13
13
  };
14
14
  exports.validateQueueKey = validateQueueKey;
15
- const validatePushSettings = (settings) => {
16
- if ((settings.delayInSeconds && settings.delayInSeconds > 900) || settings.delayInSeconds < 0) {
15
+ const validatePushSettings = (event) => {
16
+ if ((event.delayInSeconds && event.delayInSeconds > 900) || (event.delayInSeconds && event.delayInSeconds < 0)) {
17
17
  throw new errors_1.InvalidPushSettingsError(text_1.Text.error.invalidDelayInSecondsSetting);
18
18
  }
19
19
  };
20
20
  exports.validatePushSettings = validatePushSettings;
21
- const validatePushPayloads = (payloads) => {
22
- if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) {
21
+ function validatePushEvents(arg) {
22
+ const events = Array.isArray(arg) ? arg : [arg];
23
+ if (events.length === 0) {
23
24
  throw new errors_1.NoEventsToPushError(text_1.Text.error.noEventsPushed);
24
25
  }
25
- if (Array.isArray(payloads) && payloads.length > MAXIMUM_EVENTS) {
26
+ if (events.length > MAXIMUM_EVENTS) {
26
27
  throw new errors_1.TooManyEventsError(text_1.Text.error.maxEventsAllowed(MAXIMUM_EVENTS));
27
28
  }
28
- const payloadSizeKB = Buffer.byteLength(JSON.stringify(payloads)) / 1024;
29
+ for (const event of events) {
30
+ if (typeof event !== 'object' || Array.isArray(event) || event === null) {
31
+ throw new errors_1.InvalidPayloadError(text_1.Text.error.invalidEvent);
32
+ }
33
+ if (typeof event.body !== 'object' || Array.isArray(event.body) || event.body === null) {
34
+ throw new errors_1.InvalidPayloadError(text_1.Text.error.invalidEventBody);
35
+ }
36
+ (0, exports.validatePushSettings)(event);
37
+ }
38
+ const payloadSizeKB = Buffer.byteLength(JSON.stringify(events)) / 1024;
29
39
  if (payloadSizeKB > MAXIMUM_PAYLOAD_SIZE_KB) {
30
40
  throw new errors_1.PayloadTooBigError(text_1.Text.error.maxPayloadAllowed(MAXIMUM_PAYLOAD_SIZE_KB));
31
41
  }
32
- };
33
- exports.validatePushPayloads = validatePushPayloads;
42
+ return events;
43
+ }
44
+ exports.validatePushEvents = validatePushEvents;
34
45
  const validateGetStatsPayload = (getStatsRequest) => {
35
46
  if (!getStatsRequest.jobId) {
36
47
  throw new errors_1.JobDoesNotExistError(text_1.Text.error.jobIdEmpty);
@@ -67,7 +78,7 @@ const validateAPIResponse = async (response, expectedSuccessStatus) => {
67
78
  }
68
79
  };
69
80
  exports.validateAPIResponse = validateAPIResponse;
70
- const validatePushAPIResponse = async (response, requestBody) => {
81
+ const validatePushAPIResponse = async (requestBody, response, result) => {
71
82
  if (response.status === 413) {
72
83
  const responseBody = await response.json();
73
84
  throw new errors_1.PayloadTooBigError(responseBody.errorMessage);
@@ -75,7 +86,7 @@ const validatePushAPIResponse = async (response, requestBody) => {
75
86
  if (response.status === 202) {
76
87
  const responseBody = await response.json();
77
88
  const defaultErrorMessage = 'Failed to process some events.';
78
- const partialSuccessError = new errors_1.PartialSuccessError(defaultErrorMessage, []);
89
+ const partialSuccessError = new errors_1.PartialSuccessError(defaultErrorMessage, result, []);
79
90
  if (responseBody.failedEvents && responseBody.failedEvents.length > 0) {
80
91
  partialSuccessError.message = `Failed to process ${responseBody.failedEvents.length} event(s).`;
81
92
  partialSuccessError.failedEvents = responseBody.failedEvents.map((failedEvent) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forge/events",
3
- "version": "1.1.0-next.0",
3
+ "version": "2.0.0-next.1",
4
4
  "description": "Forge Async Event methods",
5
5
  "author": "Atlassian",
6
6
  "license": "SEE LICENSE IN LICENSE.txt",
@@ -4,7 +4,7 @@ import { JobProgress } from '../jobProgress';
4
4
  import { __requestAtlassianAsApp } from '@forge/api';
5
5
 
6
6
  jest.mock('@forge/api', () => ({
7
- __requestAtlassianAsApp: getMockFetchMethod('done', 200)
7
+ __requestAtlassianAsApp: getMockFetchMethod({ done: true }, 200)
8
8
  }));
9
9
 
10
10
  const getJobProgress = (jobId: string, apiClientMock?: any) => new JobProgress(jobId, apiClientMock);
@@ -21,8 +21,7 @@ describe('JobProgress methods', () => {
21
21
  200
22
22
  );
23
23
  const jobProgress = getJobProgress('test-queue-name#test-job-id', apiClientMock);
24
- const response = await jobProgress.getStats();
25
- const { success, inProgress, failed } = await response.json();
24
+ const { success, inProgress, failed } = await jobProgress.getStats();
26
25
  verifyApiClientCalledWith(
27
26
  apiClientMock,
28
27
  '/webhook/queue/stats/{contextAri}/{environmentId}/{appId}/{appVersion}',
@@ -100,7 +99,7 @@ describe('JobProgress methods', () => {
100
99
  it('should call the queue/cancel endpoint', async () => {
101
100
  const apiClientMock = getMockFetchMethod({}, 204);
102
101
  const jobProgress = getJobProgress('test-queue-name#test-job-id', apiClientMock);
103
- const response = await jobProgress.cancel();
102
+ await jobProgress.cancel();
104
103
  verifyApiClientCalledWith(
105
104
  apiClientMock,
106
105
  '/webhook/queue/cancel/{contextAri}/{environmentId}/{appId}/{appVersion}',
@@ -109,7 +108,6 @@ describe('JobProgress methods', () => {
109
108
  jobId: 'test-job-id'
110
109
  }
111
110
  );
112
- expect(response.status).toEqual(204);
113
111
  });
114
112
 
115
113
  it('should throw JobDoesNotExistError', async () => {
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  InternalServerError,
3
3
  InvalidQueueNameError,
4
+ InvalidPayloadError,
4
5
  NoEventsToPushError,
5
6
  PartialSuccessError,
6
7
  PayloadTooBigError,
@@ -30,21 +31,57 @@ describe('Queue methods', () => {
30
31
  });
31
32
  });
32
33
 
34
+ const PAYLOAD = { body: { page: 1 } } as const;
35
+
33
36
  describe('push', () => {
34
- it('should call the queue/publish endpoint', async () => {
37
+ it.each([
38
+ ['single payload', PAYLOAD, [PAYLOAD]],
39
+ ['payload array', [PAYLOAD, PAYLOAD], [PAYLOAD, PAYLOAD]]
40
+ ])('should call the queue/publish endpoint when given %s', async (_, payload, expectedPayload) => {
41
+ const apiClientMock = getMockFetchMethod();
42
+ const queue = getQueue('name', apiClientMock);
43
+ await queue.push(payload);
44
+ verifyApiClientCalledPushPathWith(apiClientMock, expectedPayload, 'name');
45
+ });
46
+
47
+ it.each([
48
+ ['number', 1],
49
+ ['string', 'test'],
50
+ ['boolean', true],
51
+ ['array', [1, 2, 3]],
52
+ ['null', null],
53
+ ['undefined', undefined],
54
+ ['array with non-object', [1]],
55
+ ['array with an array', [[2, 3]]]
56
+ ])('rejects non-object (%s) event', async (_, payload) => {
57
+ const apiClientMock = getMockFetchMethod();
58
+ const queue = getQueue('name', apiClientMock);
59
+ // @ts-expect-error Testing invalid payload type on purpose
60
+ await expect(queue.push(payload)).rejects.toThrow(new InvalidPayloadError(`Event must be an object.`));
61
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
62
+ });
63
+
64
+ it.each([
65
+ ['number', 1],
66
+ ['string', 'test'],
67
+ ['boolean', true],
68
+ ['array', [1, 2, 3]],
69
+ ['null', null],
70
+ ['undefined', undefined],
71
+ ['array with non-object', [1]],
72
+ ['array with an array', [[2, 3]]]
73
+ ])('rejects non-object (%s) event body', async (_, body) => {
35
74
  const apiClientMock = getMockFetchMethod();
36
75
  const queue = getQueue('name', apiClientMock);
37
- const payload = {
38
- page: 1
39
- };
40
- await queue.push([payload]);
41
- verifyApiClientCalledPushPathWith(apiClientMock, [payload], 'name');
76
+ // @ts-expect-error Testing invalid payload type on purpose
77
+ await expect(queue.push({ body })).rejects.toThrow(new InvalidPayloadError(`Event body must be an object.`));
78
+ expect(apiClientMock).toHaveBeenCalledTimes(0);
42
79
  });
43
80
 
44
81
  it('should throw InvalidPushSettingsError for delay', async () => {
45
82
  const apiClientMock = getMockFetchMethod();
46
83
  const queue = getQueue('name', apiClientMock);
47
- await expect(queue.push(1, { delayInSeconds: 901 })).rejects.toThrow(
84
+ await expect(queue.push({ ...PAYLOAD, delayInSeconds: 901 })).rejects.toThrow(
48
85
  new InvalidPushSettingsError(`The delayInSeconds setting must be between 0 and 900.`)
49
86
  );
50
87
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -60,7 +97,7 @@ describe('Queue methods', () => {
60
97
  it('should throw TooManyEventsError', async () => {
61
98
  const apiClientMock = getMockFetchMethod();
62
99
  const queue = getQueue('name', apiClientMock);
63
- await expect(queue.push([...Array(51).keys()])).rejects.toThrow(
100
+ await expect(queue.push(Array(51).fill(PAYLOAD))).rejects.toThrow(
64
101
  new TooManyEventsError(`This push contains more than the 50 events allowed.`)
65
102
  );
66
103
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -69,7 +106,7 @@ describe('Queue methods', () => {
69
106
  it('should throw PayloadTooBigError', async () => {
70
107
  const apiClientMock = getMockFetchMethod();
71
108
  const queue = getQueue('name', apiClientMock);
72
- await expect(queue.push('x'.repeat(201 * 1024))).rejects.toThrow(
109
+ await expect(queue.push({ body: { content: 'x'.repeat(201 * 1024) } })).rejects.toThrow(
73
110
  new PayloadTooBigError(`The maximum payload size is 200KB.`)
74
111
  );
75
112
  expect(apiClientMock).toHaveBeenCalledTimes(0);
@@ -78,19 +115,17 @@ describe('Queue methods', () => {
78
115
  it('should throw RateLimitError', async () => {
79
116
  const apiClientMock = getMockFetchMethod({}, 429);
80
117
  const queue = getQueue('name', apiClientMock);
81
- const payload = [...Array(10).keys()];
82
- await expect(queue.push(payload)).rejects.toThrow(new RateLimitError(`Too many requests.`));
83
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
118
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new RateLimitError(`Too many requests.`));
119
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
84
120
  });
85
121
 
86
122
  it('should throw InvocationLimitReachedError', async () => {
87
123
  const apiClientMock = getMockFetchMethod({}, 405);
88
124
  const queue = getQueue('name', apiClientMock);
89
- const payload = [...Array(5).keys()];
90
- await expect(queue.push(payload)).rejects.toThrow(
125
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
91
126
  new InvocationLimitReachedError(`The limit on cyclic invocation has been reached.`)
92
127
  );
93
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
128
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
94
129
  });
95
130
 
96
131
  it('should throw PartialSuccessError when there are failed events', async () => {
@@ -112,27 +147,37 @@ describe('Queue methods', () => {
112
147
  const queue = getQueue('name', apiClientMock);
113
148
  const payload = [
114
149
  {
115
- content: 'payload-1'
150
+ body: {
151
+ content: 'payload-1'
152
+ }
116
153
  },
117
154
  {
118
- content: 'payload-2'
155
+ body: {
156
+ content: 'payload-2'
157
+ }
119
158
  },
120
159
  {
121
- content: 'payload-3'
160
+ body: {
161
+ content: 'payload-3'
162
+ }
122
163
  }
123
164
  ];
124
165
  await expect(queue.push(payload)).rejects.toThrow(
125
- new PartialSuccessError(`Failed to process 2 event(s).`, [
166
+ new PartialSuccessError(`Failed to process 2 event(s).`, { jobId: 'some-job-id' }, [
126
167
  {
127
168
  errorMessage: 'failed-1',
128
- payload: {
129
- content: 'payload-1'
169
+ event: {
170
+ body: {
171
+ content: 'payload-1'
172
+ }
130
173
  }
131
174
  },
132
175
  {
133
176
  errorMessage: 'failed-3',
134
- payload: {
135
- content: 'payload-3'
177
+ event: {
178
+ body: {
179
+ content: 'payload-3'
180
+ }
136
181
  }
137
182
  }
138
183
  ])
@@ -150,17 +195,23 @@ describe('Queue methods', () => {
150
195
  const queue = getQueue('name', apiClientMock);
151
196
  const payload = [
152
197
  {
153
- content: 'payload-1'
198
+ body: {
199
+ content: 'payload-1'
200
+ }
154
201
  },
155
202
  {
156
- content: 'payload-2'
203
+ body: {
204
+ content: 'payload-2'
205
+ }
157
206
  },
158
207
  {
159
- content: 'payload-3'
208
+ body: {
209
+ content: 'payload-3'
210
+ }
160
211
  }
161
212
  ];
162
213
  await expect(queue.push(payload)).rejects.toThrow(
163
- new PartialSuccessError(`Failed to create stats for job name#12345`, [])
214
+ new PartialSuccessError(`Failed to create stats for job name#12345`, { jobId: 'some-job-id' }, [])
164
215
  );
165
216
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
166
217
  });
@@ -181,21 +232,31 @@ describe('Queue methods', () => {
181
232
  const queue = getQueue('name', apiClientMock);
182
233
  const payload = [
183
234
  {
184
- content: 'payload-1'
235
+ body: {
236
+ content: 'payload-1'
237
+ }
185
238
  },
186
239
  {
187
- content: 'payload-2'
240
+ body: {
241
+ content: 'payload-2'
242
+ }
188
243
  }
189
244
  ];
190
245
  await expect(queue.push(payload)).rejects.toThrow(
191
- new PartialSuccessError(`Failed to process 1 event(s). Failed to create stats for job name#12345`, [
192
- {
193
- errorMessage: 'failed-1',
194
- payload: {
195
- content: 'payload-1'
246
+ new PartialSuccessError(
247
+ `Failed to process 1 event(s). Failed to create stats for job name#12345`,
248
+ { jobId: 'some-job-id' },
249
+ [
250
+ {
251
+ errorMessage: 'failed-1',
252
+ event: {
253
+ body: {
254
+ content: 'payload-1'
255
+ }
256
+ }
196
257
  }
197
- }
198
- ])
258
+ ]
259
+ )
199
260
  );
200
261
  verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
201
262
  });
@@ -210,28 +271,26 @@ describe('Queue methods', () => {
210
271
  500
211
272
  );
212
273
  const queue = getQueue('name', apiClientMock);
213
- const payload = [...Array(9).keys()];
214
- await expect(queue.push(payload)).rejects.toThrow(
274
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(
215
275
  new InternalServerError(
216
276
  `500 Status Text: AWS SQS timed out`,
217
277
  500,
218
278
  'The request processing has failed because of an unknown error, exception or failure'
219
279
  )
220
280
  );
221
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
281
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
222
282
  });
223
283
 
224
284
  it('should throw InternalServerError for error response without response body', async () => {
225
285
  const apiClientMock = getApiClientMockWithoutResponseBody(504, 'Gateway Timeout');
226
286
  const queue = getQueue('name', apiClientMock);
227
- const payload = [1];
228
- await expect(queue.push(1)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
229
- verifyApiClientCalledPushPathWith(apiClientMock, payload, 'name');
287
+ await expect(queue.push(PAYLOAD)).rejects.toThrow(new InternalServerError(`504 Gateway Timeout`, 504));
288
+ verifyApiClientCalledPushPathWith(apiClientMock, [PAYLOAD], 'name');
230
289
  });
231
290
 
232
291
  it('requests stargate if no api client is provided', async () => {
233
292
  const queue = getQueue('queue');
234
- await queue.push({ test: 'stargate' });
293
+ await queue.push({ body: { test: 'stargate' } });
235
294
  expect(__requestAtlassianAsApp).toHaveBeenCalledTimes(1);
236
295
  });
237
296
  });
@@ -40,7 +40,7 @@ export const verifyApiClientCalledPushPathWith = (
40
40
  ) => {
41
41
  verifyApiClientCalledWith(apiClientMock, '/webhook/queue/publish/{contextAri}/{environmentId}/{appId}/{appVersion}', {
42
42
  queueName: name,
43
- schema: 'ari:cloud:ecosystem::forge/app-event',
43
+ schema: 'ari:cloud:ecosystem::forge/app-event-2',
44
44
  type: 'avi:forge:app:event',
45
45
  payload: payloads
46
46
  });
package/src/errors.ts CHANGED
@@ -1,51 +1,36 @@
1
- import { FailedEvent } from './types';
1
+ import { FailedEvent, PushResult } from './types';
2
2
 
3
- export class InvalidPushSettingsError extends Error {
3
+ export class EventsError extends Error {
4
4
  constructor(message: string) {
5
5
  super(message);
6
6
  }
7
7
  }
8
8
 
9
- export class InvalidQueueNameError extends Error {
10
- constructor(message: string) {
11
- super(message);
12
- }
13
- }
9
+ export class InvalidPushSettingsError extends EventsError {}
14
10
 
15
- export class TooManyEventsError extends Error {
16
- constructor(message: string) {
17
- super(message);
18
- }
19
- }
11
+ export class InvalidQueueNameError extends EventsError {}
20
12
 
21
- export class PayloadTooBigError extends Error {
22
- constructor(message: string) {
23
- super(message);
24
- }
25
- }
13
+ export class InvalidPayloadError extends EventsError {}
26
14
 
27
- export class NoEventsToPushError extends Error {
28
- constructor(message: string) {
29
- super(message);
30
- }
31
- }
15
+ export class TooManyEventsError extends EventsError {}
32
16
 
33
- export class RateLimitError extends Error {
34
- constructor(message: string) {
35
- super(message);
36
- }
37
- }
17
+ export class PayloadTooBigError extends EventsError {}
38
18
 
39
- export class PartialSuccessError extends Error {
40
- constructor(message: string, failedEvents: FailedEvent[]) {
19
+ export class NoEventsToPushError extends EventsError {}
20
+
21
+ export class RateLimitError extends EventsError {}
22
+
23
+ export class PartialSuccessError extends EventsError {
24
+ constructor(
25
+ message: string,
26
+ public result: PushResult,
27
+ public failedEvents: FailedEvent[]
28
+ ) {
41
29
  super(message);
42
- this.failedEvents = failedEvents;
43
30
  }
44
-
45
- failedEvents: FailedEvent[];
46
31
  }
47
32
 
48
- export class InternalServerError extends Error {
33
+ export class InternalServerError extends EventsError {
49
34
  constructor(message: string, errorCode?: number, details?: string) {
50
35
  super(message);
51
36
  this.errorCode = errorCode;
@@ -56,14 +41,6 @@ export class InternalServerError extends Error {
56
41
  details?: string;
57
42
  }
58
43
 
59
- export class JobDoesNotExistError extends Error {
60
- constructor(message: string) {
61
- super(message);
62
- }
63
- }
44
+ export class JobDoesNotExistError extends EventsError {}
64
45
 
65
- export class InvocationLimitReachedError extends Error {
66
- constructor(message: string) {
67
- super(message);
68
- }
69
- }
46
+ export class InvocationLimitReachedError extends EventsError {}
package/src/index.ts CHANGED
@@ -11,11 +11,12 @@ export {
11
11
  InvalidPushSettingsError,
12
12
  InvocationLimitReachedError
13
13
  } from './errors';
14
- export { JobProgress } from './jobProgress';
14
+ export { JobProgress, JobStats } from './jobProgress';
15
15
  export { QueueResponse } from './queueResponse';
16
16
  export { InvocationError } from './invocationError';
17
17
  export { InvocationErrorCode } from './invocationErrorCode';
18
18
  export { RetryOptions } from './retryOptions';
19
+ export { AsyncEvent, PushEvent, PushResult } from './types';
19
20
  export {
20
21
  appEvents,
21
22
  AppEvent,
@@ -1,4 +1,4 @@
1
- import { __requestAtlassianAsApp, FetchMethod, APIResponse } from '@forge/api';
1
+ import { __requestAtlassianAsApp, FetchMethod } from '@forge/api';
2
2
  import { CANCEL_JOB_PATH, GET_STATS_PATH, post } from './queries';
3
3
  import {
4
4
  validateCancelJobAPIResponse,
@@ -8,13 +8,19 @@ import {
8
8
  } from './validators';
9
9
  import { CancelJobRequest, GetStatsRequest } from './types';
10
10
 
11
+ export type JobStats = {
12
+ success: number;
13
+ inProgress: number;
14
+ failed: number;
15
+ };
16
+
11
17
  export class JobProgress {
12
18
  constructor(
13
19
  private readonly id: string,
14
20
  private readonly apiClient: FetchMethod = __requestAtlassianAsApp
15
21
  ) {}
16
22
 
17
- async getStats(): Promise<APIResponse> {
23
+ async getStats(): Promise<JobStats> {
18
24
  const [queueName, jobId] = this.id.split('#');
19
25
  const getStatsRequest: GetStatsRequest = {
20
26
  queueName: queueName,
@@ -26,10 +32,11 @@ export class JobProgress {
26
32
 
27
33
  const response = await post(GET_STATS_PATH, getStatsRequest, this.apiClient);
28
34
  await validateGetStatsAPIResponse(response, getStatsRequest);
29
- return response;
35
+ const { success, inProgress, failed } = await response.json();
36
+ return { success, inProgress, failed };
30
37
  }
31
38
 
32
- async cancel(): Promise<APIResponse> {
39
+ async cancel(): Promise<void> {
33
40
  const [queueName, jobId] = this.id.split('#');
34
41
  const cancelJobRequest: CancelJobRequest = {
35
42
  queueName: queueName,
@@ -41,6 +48,5 @@ export class JobProgress {
41
48
 
42
49
  const response = await post(CANCEL_JOB_PATH, cancelJobRequest, this.apiClient);
43
50
  await validateCancelJobAPIResponse(response, cancelJobRequest);
44
- return response;
45
51
  }
46
52
  }