@forge/events
Advanced tools
Comparing version 0.0.0-experimental-906222c to 0.0.0-experimental-9b81865
321
CHANGELOG.md
# @forge/events | ||
## 0.0.0-experimental-906222c | ||
## 0.0.0-experimental-9b81865 | ||
### Minor Changes | ||
- 9b81865: Add NodeJS runtime support for Events API | ||
### Patch Changes | ||
- Updated dependencies [9b81865] | ||
- @forge/api@0.0.0-experimental-9b81865 | ||
## 0.8.0-next.0 | ||
### Minor Changes | ||
- dfab69c: Add NodeJS runtime support for Events API | ||
### Patch Changes | ||
- Updated dependencies [dfab69c] | ||
- @forge/api@2.10.1-next.0 | ||
## 0.7.5 | ||
### Patch Changes | ||
- Updated dependencies [37f48c5] | ||
- Updated dependencies [e0e3587] | ||
- Updated dependencies [ca8551d] | ||
- @forge/api@2.10.0 | ||
## 0.7.5-next.1 | ||
### Patch Changes | ||
- Updated dependencies [e0e3587a] | ||
- @forge/api@2.10.0-next.1 | ||
## 0.7.5-next.0 | ||
### Patch Changes | ||
- Updated dependencies [ca8551dd] | ||
- @forge/api@2.9.2-next.0 | ||
## 0.7.4 | ||
### Patch Changes | ||
- Updated dependencies [73b929a] | ||
- Updated dependencies [8d0dc10] | ||
- Updated dependencies [7a4fa35] | ||
- @forge/api@2.9.1 | ||
## 0.7.4-next.0 | ||
### Patch Changes | ||
- Updated dependencies [7a4fa35] | ||
- @forge/api@2.9.1-next.0 | ||
## 0.7.3 | ||
### Patch Changes | ||
- Updated dependencies [e5dd325] | ||
- @forge/api@2.9.0 | ||
## 0.7.3-next.0 | ||
### Patch Changes | ||
- Updated dependencies [e5dd325d] | ||
- @forge/api@2.9.0-next.0 | ||
## 0.7.2 | ||
### Patch Changes | ||
- Updated dependencies [8e2a5a6] | ||
- @forge/api@2.8.1 | ||
## 0.7.2-next.0 | ||
### Patch Changes | ||
- Updated dependencies [8e2a5a6] | ||
- @forge/api@2.8.1-next.0 | ||
## 0.7.1 | ||
### Patch Changes | ||
- Updated dependencies [95913f6] | ||
- @forge/api@2.8.0 | ||
## 0.7.1-next.0 | ||
### Patch Changes | ||
- Updated dependencies [95913f6] | ||
- @forge/api@2.8.0-next.0 | ||
## 0.7.0 | ||
### Minor Changes | ||
- e3af95e: Fix broken test for InvocationError | ||
- d49508c: Fix broken test for InvocationError | ||
- 6529a96: Add retryData into retryOptions | ||
- 5062ab4: Fix broken test for InvocationError | ||
## 0.7.0-next.0 | ||
### Minor Changes | ||
- e3af95e: Fix broken test for InvocationError | ||
- d49508c: Fix broken test for InvocationError | ||
- 6529a96: Add retryData into retryOptions | ||
- 5062ab4: Fix broken test for InvocationError | ||
## 0.6.1 | ||
### Patch Changes | ||
- 0e0a7b4: Returning object from InvocationError to make it transferrable. | ||
- Updated dependencies [b3ee297] | ||
- @forge/api@2.7.0 | ||
## 0.6.1-next.1 | ||
### Patch Changes | ||
- Updated dependencies [b3ee2973] | ||
- @forge/api@2.7.0-next.0 | ||
## 0.6.1-next.0 | ||
### Patch Changes | ||
- 0e0a7b47: Returning object from InvocationError to make it transferrable. | ||
## 0.6.0 | ||
### Minor Changes | ||
- 0859c97: Allow app developers to return InvocationError on error from lambda function | ||
## 0.6.0-next.0 | ||
### Minor Changes | ||
- 0859c971: Allow app developers to return InvocationError on error from lambda function | ||
## 0.5.3 | ||
### Patch Changes | ||
- @forge/api@2.6.1 | ||
## 0.5.3-next.0 | ||
### Patch Changes | ||
- @forge/api@2.6.1-next.0 | ||
## 0.5.2 | ||
### Patch Changes | ||
- Updated dependencies [8571c05] | ||
- @forge/api@2.6.0 | ||
## 0.5.2-next.0 | ||
### Patch Changes | ||
- Updated dependencies [8571c05] | ||
- @forge/api@2.6.0-next.0 | ||
## 0.5.1 | ||
### Patch Changes | ||
- Updated dependencies [70e9c8c] | ||
- Updated dependencies [32d11d1] | ||
- @forge/api@2.5.0 | ||
## 0.5.1-next.0 | ||
### Patch Changes | ||
- Updated dependencies [df2cd2f] | ||
- @forge/api@2.5.0-next.0 | ||
## 0.5.0 | ||
### Minor Changes | ||
- 53e3fda: Create new API jobProgress.cancel() in forge-events package | ||
### Patch Changes | ||
- @forge/api@2.4.0 | ||
## 0.5.0-next.1 | ||
### Minor Changes | ||
- 53e3fda: Create new API jobProgress.cancel() in forge-events package | ||
## 0.4.1-next.0 | ||
### Patch Changes | ||
- Updated dependencies [de02d45] | ||
- @forge/api@2.4.0-next.0 | ||
## 0.4.0 | ||
### Minor Changes | ||
- d32350b: allow sending up to 50 events per queue.push() | ||
### Patch Changes | ||
- 8a8dccc: Update error messages | ||
## 0.4.0-next.1 | ||
### Minor Changes | ||
- d32350b: allow sending up to 50 events per queue.push() | ||
## 0.3.1-next.0 | ||
### Patch Changes | ||
- 8a8dccc: Update error messages | ||
## 0.3.0 | ||
### Minor Changes | ||
- 9fe841c: Explicitly add uuid dependency and whitelist WHP getStats endpoint | ||
- 66b6648: Add errors and validations | ||
- 9051f1d: Add @forge/api as a dependency of @forge/events | ||
- 6dbedd8: Add QueueResponse in forge-events package to allow retrying an async event. | ||
### Patch Changes | ||
- afa9ee0: Update the error messages | ||
## 0.3.0-next.4 | ||
### Patch Changes | ||
- afa9ee0: Update the error messages | ||
## 0.3.0-next.3 | ||
### Minor Changes | ||
- 9051f1d: Add @forge/api as a dependency of @forge/events | ||
## 0.3.0-next.2 | ||
### Minor Changes | ||
- 6dbedd8: Add QueueResponse in forge-events package to allow retrying an async event. | ||
## 0.3.0-next.1 | ||
### Minor Changes | ||
- 66b6648: Add errors and validations | ||
## 0.3.0-next.0 | ||
### Minor Changes | ||
- 767d40d: Explicitly add uuid dependency and whitelist WHP getStats endpoint | ||
## 0.2.0 | ||
### Minor Changes | ||
- a056b40: Add Queue.getStats() | ||
- 8dca98b: New configurable settings for Queue push API | ||
### Patch Changes | ||
- ddce3eb: Update the error message for delayInSettings error in push settings | ||
## 0.2.0-next.2 | ||
### Patch Changes | ||
- ddce3eb: Update the error message for delayInSettings error in push settings | ||
## 0.2.0-next.1 | ||
### Minor Changes | ||
- a056b40: Add Queue.getStats() | ||
## 0.2.0-next.0 | ||
### Minor Changes | ||
- 8dca98b: New configurable settings for Queue push API | ||
## 0.1.0 | ||
### Minor Changes | ||
- bfc9f8c: update stargate allowlist and new middleware for updating runtime variables in path for requestAtlassian | ||
- 0831a0a: export queue errors and rename queue param "queueName" to "key" | ||
@@ -13,2 +326,8 @@ ### Patch Changes | ||
## 0.1.0-next.2 | ||
### Minor Changes | ||
- 0831a0a: export queue errors and rename queue param "queueName" to "key" | ||
## 0.1.0-next.1 | ||
@@ -15,0 +334,0 @@ |
import { FailedEvent } from './types'; | ||
export declare class InvalidPushSettingsError extends Error { | ||
constructor(message: string); | ||
} | ||
export declare class InvalidQueueNameError extends Error { | ||
@@ -26,2 +29,8 @@ constructor(message: string); | ||
} | ||
export declare class JobDoesNotExistError extends Error { | ||
constructor(message: string); | ||
} | ||
export declare class InvocationLimitReachedError extends Error { | ||
constructor(message: string); | ||
} | ||
//# sourceMappingURL=errors.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.InternalServerError = exports.PartialSuccessError = exports.RateLimitError = exports.NoEventsToPushError = exports.PayloadTooBigError = exports.TooManyEventsError = exports.InvalidQueueNameError = void 0; | ||
exports.InvocationLimitReachedError = exports.JobDoesNotExistError = exports.InternalServerError = exports.PartialSuccessError = exports.RateLimitError = exports.NoEventsToPushError = exports.PayloadTooBigError = exports.TooManyEventsError = exports.InvalidQueueNameError = exports.InvalidPushSettingsError = void 0; | ||
class InvalidPushSettingsError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
exports.InvalidPushSettingsError = InvalidPushSettingsError; | ||
class InvalidQueueNameError extends Error { | ||
@@ -49,1 +55,13 @@ constructor(message) { | ||
exports.InternalServerError = InternalServerError; | ||
class JobDoesNotExistError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
exports.JobDoesNotExistError = JobDoesNotExistError; | ||
class InvocationLimitReachedError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
exports.InvocationLimitReachedError = InvocationLimitReachedError; |
export { Queue } from './queue'; | ||
export { InvalidQueueNameError, TooManyEventsError, PayloadTooBigError, NoEventsToPushError, RateLimitError, PartialSuccessError, InternalServerError, JobDoesNotExistError, InvalidPushSettingsError, InvocationLimitReachedError } from './errors'; | ||
export { JobProgress } from './jobProgress'; | ||
export { QueueResponse } from './queueResponse'; | ||
export { InvocationError } from './invocationError'; | ||
export { InvocationErrorCode } from './invocationErrorCode'; | ||
export { RetryOptions } from './retryOptions'; | ||
//# sourceMappingURL=index.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.InvocationErrorCode = exports.InvocationError = exports.QueueResponse = exports.JobProgress = exports.InvocationLimitReachedError = exports.InvalidPushSettingsError = exports.JobDoesNotExistError = exports.InternalServerError = exports.PartialSuccessError = exports.RateLimitError = exports.NoEventsToPushError = exports.PayloadTooBigError = exports.TooManyEventsError = exports.InvalidQueueNameError = exports.Queue = void 0; | ||
var queue_1 = require("./queue"); | ||
Object.defineProperty(exports, "Queue", { enumerable: true, get: function () { return queue_1.Queue; } }); | ||
var errors_1 = require("./errors"); | ||
Object.defineProperty(exports, "InvalidQueueNameError", { enumerable: true, get: function () { return errors_1.InvalidQueueNameError; } }); | ||
Object.defineProperty(exports, "TooManyEventsError", { enumerable: true, get: function () { return errors_1.TooManyEventsError; } }); | ||
Object.defineProperty(exports, "PayloadTooBigError", { enumerable: true, get: function () { return errors_1.PayloadTooBigError; } }); | ||
Object.defineProperty(exports, "NoEventsToPushError", { enumerable: true, get: function () { return errors_1.NoEventsToPushError; } }); | ||
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_1.RateLimitError; } }); | ||
Object.defineProperty(exports, "PartialSuccessError", { enumerable: true, get: function () { return errors_1.PartialSuccessError; } }); | ||
Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return errors_1.InternalServerError; } }); | ||
Object.defineProperty(exports, "JobDoesNotExistError", { enumerable: true, get: function () { return errors_1.JobDoesNotExistError; } }); | ||
Object.defineProperty(exports, "InvalidPushSettingsError", { enumerable: true, get: function () { return errors_1.InvalidPushSettingsError; } }); | ||
Object.defineProperty(exports, "InvocationLimitReachedError", { enumerable: true, get: function () { return errors_1.InvocationLimitReachedError; } }); | ||
var jobProgress_1 = require("./jobProgress"); | ||
Object.defineProperty(exports, "JobProgress", { enumerable: true, get: function () { return jobProgress_1.JobProgress; } }); | ||
var queueResponse_1 = require("./queueResponse"); | ||
Object.defineProperty(exports, "QueueResponse", { enumerable: true, get: function () { return queueResponse_1.QueueResponse; } }); | ||
var invocationError_1 = require("./invocationError"); | ||
Object.defineProperty(exports, "InvocationError", { enumerable: true, get: function () { return invocationError_1.InvocationError; } }); | ||
var invocationErrorCode_1 = require("./invocationErrorCode"); | ||
Object.defineProperty(exports, "InvocationErrorCode", { enumerable: true, get: function () { return invocationErrorCode_1.InvocationErrorCode; } }); |
@@ -1,4 +0,6 @@ | ||
import { PushBodyParams, APIRequest } from './types'; | ||
export declare const getPushBody: (params: PushBodyParams) => APIRequest; | ||
import { APIRequest, APIResponse, FetchMethod } from './types'; | ||
export declare const PUSH_PATH = "/webhook/queue/publish/{cloudId}/{environmentId}/{appId}/{appVersion}"; | ||
export declare const GET_STATS_PATH = "/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}"; | ||
export declare const CANCEL_JOB_PATH = "/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}"; | ||
export declare const post: (endpoint: string, body: APIRequest, apiClient: FetchMethod) => Promise<APIResponse>; | ||
//# sourceMappingURL=queries.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.PUSH_PATH = exports.getPushBody = void 0; | ||
exports.getPushBody = (params) => ({ | ||
queueName: params.queueName, | ||
type: params.type, | ||
schema: params.schema, | ||
payload: params.payload, | ||
time: new Date().toISOString() | ||
}); | ||
exports.post = exports.CANCEL_JOB_PATH = exports.GET_STATS_PATH = exports.PUSH_PATH = void 0; | ||
exports.PUSH_PATH = '/webhook/queue/publish/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
exports.GET_STATS_PATH = '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
exports.CANCEL_JOB_PATH = '/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
const post = async (endpoint, body, apiClient) => { | ||
const request = { | ||
method: 'POST', | ||
body: JSON.stringify(body), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
return await apiClient(endpoint, request); | ||
}; | ||
exports.post = post; |
@@ -1,2 +0,3 @@ | ||
import { APIResponse, FetchMethod, Payload, QueueParams } from './types'; | ||
import { FetchMethod, Payload, QueueParams, PushSettings } from './types'; | ||
import { JobProgress } from './jobProgress'; | ||
export declare class Queue { | ||
@@ -6,6 +7,5 @@ private readonly apiClient; | ||
constructor(queueParams: QueueParams, apiClient?: FetchMethod); | ||
push(payloads: Payload | Payload[]): Promise<APIResponse>; | ||
private query; | ||
private buildRequest; | ||
push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string>; | ||
getJob(jobId: string): JobProgress; | ||
} | ||
//# sourceMappingURL=queue.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Queue = void 0; | ||
const tslib_1 = require("tslib"); | ||
const queries_1 = require("./queries"); | ||
const validators_1 = require("./validators"); | ||
async function getResponseBody(response, body) { | ||
await validators_1.validateAPIResponse(response, body); | ||
return response; | ||
} | ||
const v4_1 = tslib_1.__importDefault(require("uuid/v4")); | ||
const jobProgress_1 = require("./jobProgress"); | ||
const api_1 = require("@forge/api"); | ||
class Queue { | ||
constructor(queueParams, apiClient) { | ||
validators_1.validateQueueName(queueParams.queueName); | ||
(0, validators_1.validateQueueKey)(queueParams.key); | ||
this.queueParams = queueParams; | ||
this.apiClient = apiClient || global.api.asApp().__requestAtlassian; | ||
this.apiClient = apiClient || (0, api_1.createRequestStargateAsApp)(); | ||
} | ||
async push(payloads) { | ||
validators_1.validatePayloads(payloads); | ||
const queryParams = { | ||
async push(payloads, pushSettings) { | ||
(0, validators_1.validatePushPayloads)(payloads); | ||
const queueName = this.queueParams.key; | ||
const jobId = (0, v4_1.default)(); | ||
const pushRequest = { | ||
queueName: queueName, | ||
jobId: jobId, | ||
type: 'avi:forge:app:event', | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
payload: Array.isArray(payloads) ? payloads : [payloads], | ||
queueName: this.queueParams.queueName, | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
type: 'avi:forge:app:event' | ||
time: new Date().toISOString() | ||
}; | ||
const requestBody = queries_1.getPushBody(queryParams); | ||
return this.query(queries_1.PUSH_PATH, requestBody); | ||
if (pushSettings) { | ||
(0, validators_1.validatePushSettings)(pushSettings); | ||
if (pushSettings.delayInSeconds) { | ||
pushRequest.delayInSeconds = pushSettings.delayInSeconds; | ||
} | ||
} | ||
const response = await (0, queries_1.post)(queries_1.PUSH_PATH, pushRequest, this.apiClient); | ||
await (0, validators_1.validatePushAPIResponse)(response, pushRequest); | ||
return `${queueName}#${jobId}`; | ||
} | ||
async query(endpoint, body) { | ||
const response = await this.apiClient(endpoint, this.buildRequest(body)); | ||
return await getResponseBody(response, body); | ||
getJob(jobId) { | ||
return new jobProgress_1.JobProgress(jobId, this.apiClient); | ||
} | ||
buildRequest(requestBody) { | ||
return { | ||
method: 'POST', | ||
body: JSON.stringify(requestBody), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
} | ||
} | ||
exports.Queue = Queue; |
@@ -7,12 +7,17 @@ import { RequestInit, Response } from 'node-fetch'; | ||
}; | ||
export interface PushSettings { | ||
delayInSeconds: number; | ||
} | ||
export interface QueueParams { | ||
queueName: string; | ||
key: string; | ||
} | ||
export interface PushBodyParams { | ||
export interface PushRequest extends APIRequest { | ||
payload: Payload[]; | ||
queueName: string; | ||
schema: string; | ||
type: string; | ||
delayInSeconds?: number; | ||
} | ||
export interface APIRequest extends PushBodyParams { | ||
export interface APIRequest { | ||
queueName: string; | ||
jobId: string; | ||
time: string; | ||
@@ -24,2 +29,4 @@ } | ||
} | ||
export declare type GetStatsRequest = APIRequest; | ||
export declare type CancelJobRequest = APIRequest; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -1,5 +0,11 @@ | ||
import { APIResponse, Payload, APIRequest } from './types'; | ||
export declare const validateQueueName: (queueName: string) => void; | ||
export declare const validatePayloads: (payloads: Payload | Payload[]) => void; | ||
export declare const validateAPIResponse: (response: APIResponse, requestBody: APIRequest) => Promise<void>; | ||
import { APIResponse, CancelJobRequest, GetStatsRequest, Payload, PushRequest, PushSettings } from './types'; | ||
export declare const validateQueueKey: (queueName: string) => void; | ||
export declare const validatePushSettings: (settings: PushSettings) => void; | ||
export declare const validatePushPayloads: (payloads: Payload | Payload[]) => void; | ||
export declare const validateGetStatsPayload: (getStatsRequest: GetStatsRequest) => void; | ||
export declare const validateCancelJobRequest: (cancelJobRequest: CancelJobRequest) => void; | ||
export declare const validateAPIResponse: (response: APIResponse, expectedSuccessStatus: number) => Promise<void>; | ||
export declare const validatePushAPIResponse: (response: APIResponse, requestBody: PushRequest) => Promise<void>; | ||
export declare const validateGetStatsAPIResponse: (response: APIResponse, getStatsRequest: GetStatsRequest) => Promise<void>; | ||
export declare const validateCancelJobAPIResponse: (response: APIResponse, cancelJobRequest: GetStatsRequest) => Promise<void>; | ||
//# sourceMappingURL=validators.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateAPIResponse = exports.validatePayloads = exports.validateQueueName = void 0; | ||
exports.validateCancelJobAPIResponse = exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validateCancelJobRequest = exports.validateGetStatsPayload = exports.validatePushPayloads = exports.validatePushSettings = exports.validateQueueKey = void 0; | ||
const errors_1 = require("./errors"); | ||
const text_1 = require("./text"); | ||
const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/; | ||
const MAXIMUM_EVENTS = 10; | ||
const MAXIMUM_EVENTS = 50; | ||
const MAXIMUM_PAYLOAD_SIZE_KB = 200; | ||
exports.validateQueueName = (queueName) => { | ||
const validateQueueKey = (queueName) => { | ||
if (!queueName || !VALID_QUEUE_NAME_PATTERN.test(queueName)) { | ||
throw new errors_1.InvalidQueueNameError('Queue name can only contain alphanumeric, dash and underscore characters'); | ||
throw new errors_1.InvalidQueueNameError(text_1.Text.error.invalidQueueName); | ||
} | ||
}; | ||
exports.validatePayloads = (payloads) => { | ||
exports.validateQueueKey = validateQueueKey; | ||
const validatePushSettings = (settings) => { | ||
if ((settings.delayInSeconds && settings.delayInSeconds > 900) || settings.delayInSeconds < 0) { | ||
throw new errors_1.InvalidPushSettingsError(text_1.Text.error.invalidDelayInSecondsSetting); | ||
} | ||
}; | ||
exports.validatePushSettings = validatePushSettings; | ||
const validatePushPayloads = (payloads) => { | ||
if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) { | ||
throw new errors_1.NoEventsToPushError(`No events pushed`); | ||
throw new errors_1.NoEventsToPushError(text_1.Text.error.noEventsPushed); | ||
} | ||
if (Array.isArray(payloads) && payloads.length > MAXIMUM_EVENTS) { | ||
throw new errors_1.TooManyEventsError(`Maximum of ${MAXIMUM_EVENTS} events allowed in a single push`); | ||
throw new errors_1.TooManyEventsError(text_1.Text.error.maxEventsAllowed(MAXIMUM_EVENTS)); | ||
} | ||
const payloadSizeKB = Buffer.byteLength(JSON.stringify(payloads)) / 1024; | ||
if (payloadSizeKB > MAXIMUM_PAYLOAD_SIZE_KB) { | ||
throw new errors_1.PayloadTooBigError(`The maximum payload size allowed is ${MAXIMUM_PAYLOAD_SIZE_KB}KB`); | ||
throw new errors_1.PayloadTooBigError(text_1.Text.error.maxPayloadAllowed(MAXIMUM_PAYLOAD_SIZE_KB)); | ||
} | ||
}; | ||
exports.validateAPIResponse = async (response, requestBody) => { | ||
exports.validatePushPayloads = validatePushPayloads; | ||
const validateGetStatsPayload = (getStatsRequest) => { | ||
if (!getStatsRequest.jobId) { | ||
throw new errors_1.JobDoesNotExistError(text_1.Text.error.jobIdEmpty); | ||
} | ||
(0, exports.validateQueueKey)(getStatsRequest.queueName); | ||
}; | ||
exports.validateGetStatsPayload = validateGetStatsPayload; | ||
const validateCancelJobRequest = (cancelJobRequest) => { | ||
if (!cancelJobRequest.jobId) { | ||
throw new errors_1.JobDoesNotExistError(text_1.Text.error.jobIdEmpty); | ||
} | ||
(0, exports.validateQueueKey)(cancelJobRequest.queueName); | ||
}; | ||
exports.validateCancelJobRequest = validateCancelJobRequest; | ||
const validateAPIResponse = async (response, expectedSuccessStatus) => { | ||
if (response.status === 429) { | ||
throw new errors_1.RateLimitError(`Too many requests`); | ||
throw new errors_1.RateLimitError(text_1.Text.error.rateLimitError); | ||
} | ||
if (response.status === 405) { | ||
throw new errors_1.InvocationLimitReachedError(text_1.Text.error.invocationLimitReachedError); | ||
} | ||
if (response.status != expectedSuccessStatus && response.status) { | ||
let internalServerError; | ||
try { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
const errors = responseBody.errors ? `: ${responseBody.errors.join(', ')}` : ''; | ||
internalServerError = new errors_1.InternalServerError(`${response.status} ${response.statusText}${errorMessage}${errors}`, responseBody.code, responseBody.details); | ||
} | ||
catch (ignore) { | ||
internalServerError = new errors_1.InternalServerError(`${response.status} ${response.statusText}`, response.status); | ||
} | ||
throw internalServerError; | ||
} | ||
}; | ||
exports.validateAPIResponse = validateAPIResponse; | ||
const validatePushAPIResponse = async (response, requestBody) => { | ||
if (response.status === 413) { | ||
@@ -35,4 +77,7 @@ const responseBody = await response.json(); | ||
const responseBody = await response.json(); | ||
const defaultErrorMessage = 'Failed to process some events.'; | ||
const partialSuccessError = new errors_1.PartialSuccessError(defaultErrorMessage, []); | ||
if (responseBody.failedEvents && responseBody.failedEvents.length > 0) { | ||
throw new errors_1.PartialSuccessError(`Failed to process ${responseBody.failedEvents.length} events`, responseBody.failedEvents.map((failedEvent) => { | ||
partialSuccessError.message = `Failed to process ${responseBody.failedEvents.length} event(s).`; | ||
partialSuccessError.failedEvents = responseBody.failedEvents.map((failedEvent) => { | ||
return { | ||
@@ -42,13 +87,28 @@ errorMessage: failedEvent.errorMessage, | ||
}; | ||
})); | ||
}); | ||
} | ||
else { | ||
throw new errors_1.PartialSuccessError(`Failed to process events`, []); | ||
if (responseBody.errorMessage) { | ||
partialSuccessError.message = | ||
partialSuccessError.message !== defaultErrorMessage | ||
? `${partialSuccessError.message} ${responseBody.errorMessage}` | ||
: responseBody.errorMessage; | ||
} | ||
throw partialSuccessError; | ||
} | ||
if (response.status != 201 && response.status) { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
throw new errors_1.InternalServerError(`${response.status} ${response.statusText}${errorMessage}`, responseBody.code, responseBody.details); | ||
await (0, exports.validateAPIResponse)(response, 201); | ||
}; | ||
exports.validatePushAPIResponse = validatePushAPIResponse; | ||
const validateGetStatsAPIResponse = async (response, getStatsRequest) => { | ||
if (response.status === 404) { | ||
throw new errors_1.JobDoesNotExistError(text_1.Text.error.jobDoesNotExit(getStatsRequest.jobId, getStatsRequest.queueName)); | ||
} | ||
await (0, exports.validateAPIResponse)(response, 200); | ||
}; | ||
exports.validateGetStatsAPIResponse = validateGetStatsAPIResponse; | ||
const validateCancelJobAPIResponse = async (response, cancelJobRequest) => { | ||
if (response.status === 404) { | ||
throw new errors_1.JobDoesNotExistError(text_1.Text.error.jobDoesNotExit(cancelJobRequest.jobId, cancelJobRequest.queueName)); | ||
} | ||
await (0, exports.validateAPIResponse)(response, 204); | ||
}; | ||
exports.validateCancelJobAPIResponse = validateCancelJobAPIResponse; |
{ | ||
"name": "@forge/events", | ||
"version": "0.0.0-experimental-906222c", | ||
"version": "0.0.0-experimental-9b81865", | ||
"description": "Forge Async Event methods", | ||
@@ -15,4 +15,9 @@ "author": "Atlassian", | ||
"devDependencies": { | ||
"@types/node": "^12.12.63" | ||
"@types/node": "^12.12.63", | ||
"@types/uuid": "^3.4.7" | ||
}, | ||
"dependencies": { | ||
"@forge/api": "^0.0.0-experimental-9b81865", | ||
"uuid": "^3.4.0" | ||
} | ||
} |
@@ -31,3 +31,3 @@ Library for asynchronous data processing. | ||
async function demo() { | ||
const queue = new Queue({queueName: "queue-name"}, apiClient); | ||
const queue = new Queue({key: "queue-name"}, apiClient); | ||
const payloads = { | ||
@@ -34,0 +34,0 @@ page: 1 |
import { FailedEvent } from './types'; | ||
export class InvalidPushSettingsError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
} | ||
} | ||
export class InvalidQueueNameError extends Error { | ||
@@ -52,1 +58,13 @@ constructor(message: string) { | ||
} | ||
export class JobDoesNotExistError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
} | ||
} | ||
export class InvocationLimitReachedError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
} | ||
} |
export { Queue } from './queue'; | ||
export { | ||
InvalidQueueNameError, | ||
TooManyEventsError, | ||
PayloadTooBigError, | ||
NoEventsToPushError, | ||
RateLimitError, | ||
PartialSuccessError, | ||
InternalServerError, | ||
JobDoesNotExistError, | ||
InvalidPushSettingsError, | ||
InvocationLimitReachedError | ||
} from './errors'; | ||
export { JobProgress } from './jobProgress'; | ||
export { QueueResponse } from './queueResponse'; | ||
export { InvocationError } from './invocationError'; | ||
export { InvocationErrorCode } from './invocationErrorCode'; | ||
export { RetryOptions } from './retryOptions'; |
@@ -1,11 +0,17 @@ | ||
import { PushBodyParams, APIRequest } from './types'; | ||
import { APIRequest, APIResponse, FetchMethod } from './types'; | ||
export const getPushBody = (params: PushBodyParams): APIRequest => ({ | ||
queueName: params.queueName, | ||
type: params.type, | ||
schema: params.schema, | ||
payload: params.payload, | ||
time: new Date().toISOString() | ||
}); | ||
export const PUSH_PATH = '/webhook/queue/publish/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
export const GET_STATS_PATH = '/webhook/queue/stats/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
export const CANCEL_JOB_PATH = '/webhook/queue/cancel/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
export const PUSH_PATH = '/webhook/queue/publish/{cloudId}/{environmentId}/{appId}/{appVersion}'; | ||
export const post = async (endpoint: string, body: APIRequest, apiClient: FetchMethod): Promise<APIResponse> => { | ||
const request = { | ||
method: 'POST', | ||
body: JSON.stringify(body), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
return await apiClient(endpoint, request); | ||
}; |
@@ -1,10 +0,8 @@ | ||
import { getPushBody, PUSH_PATH } from './queries'; | ||
import { validateAPIResponse, validatePayloads, validateQueueName } from './validators'; | ||
import { APIResponse, FetchMethod, Payload, PushBodyParams, QueueParams, APIRequest } from './types'; | ||
import { PUSH_PATH, post } from './queries'; | ||
import { validatePushAPIResponse, validatePushPayloads, validateQueueKey, validatePushSettings } from './validators'; | ||
import { FetchMethod, Payload, QueueParams, PushSettings, PushRequest } from './types'; | ||
import uuid from 'uuid/v4'; | ||
import { JobProgress } from './jobProgress'; | ||
import { createRequestStargateAsApp } from '@forge/api'; | ||
async function getResponseBody(response: APIResponse, body: any): Promise<APIResponse> { | ||
await validateAPIResponse(response, body); | ||
return response; | ||
} | ||
export class Queue { | ||
@@ -15,34 +13,36 @@ private readonly apiClient: FetchMethod; | ||
constructor(queueParams: QueueParams, apiClient?: FetchMethod) { | ||
validateQueueName(queueParams.queueName); | ||
validateQueueKey(queueParams.key); | ||
this.queueParams = queueParams; | ||
this.apiClient = apiClient || (global as any).api.asApp().__requestAtlassian; | ||
this.apiClient = apiClient || createRequestStargateAsApp(); | ||
} | ||
async push(payloads: Payload | Payload[]): Promise<APIResponse> { | ||
validatePayloads(payloads); | ||
async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string> { | ||
validatePushPayloads(payloads); | ||
const queueName = this.queueParams.key; | ||
const jobId = uuid(); | ||
const queryParams: PushBodyParams = { | ||
const pushRequest: PushRequest = { | ||
queueName: queueName, | ||
jobId: jobId, | ||
type: 'avi:forge:app:event', | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
payload: Array.isArray(payloads) ? payloads : [payloads], | ||
queueName: this.queueParams.queueName, | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
type: 'avi:forge:app:event' | ||
time: new Date().toISOString() | ||
}; | ||
const requestBody = getPushBody(queryParams); | ||
return this.query(PUSH_PATH, requestBody); | ||
} | ||
private async query(endpoint: string, body: APIRequest): Promise<APIResponse> { | ||
const response = await this.apiClient(endpoint, this.buildRequest(body)); | ||
return await getResponseBody(response, body); | ||
if (pushSettings) { | ||
validatePushSettings(pushSettings); | ||
if (pushSettings.delayInSeconds) { | ||
pushRequest.delayInSeconds = pushSettings.delayInSeconds; | ||
} | ||
} | ||
const response = await post(PUSH_PATH, pushRequest, this.apiClient); | ||
await validatePushAPIResponse(response, pushRequest); | ||
return `${queueName}#${jobId}`; | ||
} | ||
private buildRequest(requestBody: APIRequest) { | ||
return { | ||
method: 'POST', | ||
body: JSON.stringify(requestBody), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
getJob(jobId: string): JobProgress { | ||
return new JobProgress(jobId, this.apiClient); | ||
} | ||
} |
@@ -7,14 +7,19 @@ import { RequestInit, Response } from 'node-fetch'; | ||
export interface PushSettings { | ||
delayInSeconds: number; | ||
} | ||
export interface QueueParams { | ||
queueName: string; | ||
key: string; | ||
} | ||
export interface PushBodyParams { | ||
export interface PushRequest extends APIRequest { | ||
payload: Payload[]; | ||
queueName: string; | ||
schema: string; | ||
type: string; | ||
delayInSeconds?: number; | ||
} | ||
export interface APIRequest extends PushBodyParams { | ||
export interface APIRequest { | ||
queueName: string; | ||
jobId: string; | ||
time: string; | ||
@@ -27,1 +32,4 @@ } | ||
} | ||
export type GetStatsRequest = APIRequest; | ||
export type CancelJobRequest = APIRequest; |
import { | ||
InternalServerError, | ||
InvalidQueueNameError, | ||
JobDoesNotExistError, | ||
NoEventsToPushError, | ||
@@ -8,23 +9,32 @@ PartialSuccessError, | ||
RateLimitError, | ||
TooManyEventsError | ||
TooManyEventsError, | ||
InvalidPushSettingsError, | ||
InvocationLimitReachedError | ||
} from './errors'; | ||
import { APIResponse, Payload, APIRequest } from './types'; | ||
import { Text } from './text'; | ||
import { APIResponse, CancelJobRequest, GetStatsRequest, Payload, PushRequest, PushSettings } from './types'; | ||
const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/; | ||
const MAXIMUM_EVENTS = 10; | ||
const MAXIMUM_EVENTS = 50; | ||
const MAXIMUM_PAYLOAD_SIZE_KB = 200; | ||
export const validateQueueName = (queueName: string) => { | ||
export const validateQueueKey = (queueName: string) => { | ||
if (!queueName || !VALID_QUEUE_NAME_PATTERN.test(queueName)) { | ||
throw new InvalidQueueNameError('Queue name can only contain alphanumeric, dash and underscore characters'); | ||
throw new InvalidQueueNameError(Text.error.invalidQueueName); | ||
} | ||
}; | ||
export const validatePayloads = (payloads: Payload | Payload[]) => { | ||
export const validatePushSettings = (settings: PushSettings) => { | ||
if ((settings.delayInSeconds && settings.delayInSeconds > 900) || settings.delayInSeconds < 0) { | ||
throw new InvalidPushSettingsError(Text.error.invalidDelayInSecondsSetting); | ||
} | ||
}; | ||
export const validatePushPayloads = (payloads: Payload | Payload[]) => { | ||
if (!payloads || (Array.isArray(payloads) && payloads.length === 0)) { | ||
throw new NoEventsToPushError(`No events pushed`); | ||
throw new NoEventsToPushError(Text.error.noEventsPushed); | ||
} | ||
if (Array.isArray(payloads) && payloads.length > MAXIMUM_EVENTS) { | ||
throw new TooManyEventsError(`Maximum of ${MAXIMUM_EVENTS} events allowed in a single push`); | ||
throw new TooManyEventsError(Text.error.maxEventsAllowed(MAXIMUM_EVENTS)); | ||
} | ||
@@ -34,11 +44,52 @@ | ||
if (payloadSizeKB > MAXIMUM_PAYLOAD_SIZE_KB) { | ||
throw new PayloadTooBigError(`The maximum payload size allowed is ${MAXIMUM_PAYLOAD_SIZE_KB}KB`); | ||
throw new PayloadTooBigError(Text.error.maxPayloadAllowed(MAXIMUM_PAYLOAD_SIZE_KB)); | ||
} | ||
}; | ||
export const validateAPIResponse = async (response: APIResponse, requestBody: APIRequest) => { | ||
export const validateGetStatsPayload = (getStatsRequest: GetStatsRequest) => { | ||
if (!getStatsRequest.jobId) { | ||
throw new JobDoesNotExistError(Text.error.jobIdEmpty); | ||
} | ||
validateQueueKey(getStatsRequest.queueName); | ||
}; | ||
export const validateCancelJobRequest = (cancelJobRequest: CancelJobRequest) => { | ||
if (!cancelJobRequest.jobId) { | ||
throw new JobDoesNotExistError(Text.error.jobIdEmpty); | ||
} | ||
validateQueueKey(cancelJobRequest.queueName); | ||
}; | ||
export const validateAPIResponse = async (response: APIResponse, expectedSuccessStatus: number) => { | ||
if (response.status === 429) { | ||
throw new RateLimitError(`Too many requests`); | ||
throw new RateLimitError(Text.error.rateLimitError); | ||
} | ||
if (response.status === 405) { | ||
throw new InvocationLimitReachedError(Text.error.invocationLimitReachedError); | ||
} | ||
if (response.status != expectedSuccessStatus && response.status) { | ||
//Catch all errors from server that we have not handled | ||
let internalServerError; | ||
try { | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
const errors = responseBody.errors ? `: ${responseBody.errors.join(', ')}` : ''; //Dropwizard returns an array of errors when request body validation failed | ||
internalServerError = new InternalServerError( | ||
`${response.status} ${response.statusText}${errorMessage}${errors}`, | ||
responseBody.code, | ||
responseBody.details | ||
); | ||
} catch (ignore) { | ||
//response body is not a json | ||
internalServerError = new InternalServerError(`${response.status} ${response.statusText}`, response.status); | ||
} | ||
throw internalServerError; | ||
} | ||
}; | ||
export const validatePushAPIResponse = async (response: APIResponse, requestBody: PushRequest) => { | ||
if (response.status === 413) { | ||
@@ -52,27 +103,43 @@ //Server can return this error response if it has a different max payload size and max number of events limits | ||
const responseBody = await response.json(); | ||
const defaultErrorMessage = 'Failed to process some events.'; | ||
const partialSuccessError = new PartialSuccessError(defaultErrorMessage, []); | ||
if (responseBody.failedEvents && responseBody.failedEvents.length > 0) { | ||
throw new PartialSuccessError( | ||
`Failed to process ${responseBody.failedEvents.length} events`, | ||
responseBody.failedEvents.map((failedEvent: any) => { | ||
return { | ||
errorMessage: failedEvent.errorMessage, | ||
payload: requestBody.payload[+failedEvent.index] | ||
}; | ||
}) | ||
); | ||
} else { | ||
throw new PartialSuccessError(`Failed to process events`, []); | ||
partialSuccessError.message = `Failed to process ${responseBody.failedEvents.length} event(s).`; | ||
partialSuccessError.failedEvents = responseBody.failedEvents.map((failedEvent: any) => { | ||
return { | ||
errorMessage: failedEvent.errorMessage, | ||
payload: requestBody.payload[+failedEvent.index] | ||
}; | ||
}); | ||
} | ||
if (responseBody.errorMessage) { | ||
//Append any extra error message from backend | ||
partialSuccessError.message = | ||
partialSuccessError.message !== defaultErrorMessage | ||
? `${partialSuccessError.message} ${responseBody.errorMessage}` | ||
: responseBody.errorMessage; | ||
} | ||
throw partialSuccessError; | ||
} | ||
if (response.status != 201 && response.status) { | ||
//Catch all errors from server that we have not handled | ||
const responseBody = await response.json(); | ||
const errorMessage = responseBody.message ? `: ${responseBody.message}` : ''; | ||
throw new InternalServerError( | ||
`${response.status} ${response.statusText}${errorMessage}`, | ||
responseBody.code, | ||
responseBody.details | ||
); | ||
await validateAPIResponse(response, 201); | ||
}; | ||
export const validateGetStatsAPIResponse = async (response: APIResponse, getStatsRequest: GetStatsRequest) => { | ||
if (response.status === 404) { | ||
throw new JobDoesNotExistError(Text.error.jobDoesNotExit(getStatsRequest.jobId, getStatsRequest.queueName)); | ||
} | ||
await validateAPIResponse(response, 200); | ||
}; | ||
export const validateCancelJobAPIResponse = async (response: APIResponse, cancelJobRequest: GetStatsRequest) => { | ||
if (response.status === 404) { | ||
throw new JobDoesNotExistError(Text.error.jobDoesNotExit(cancelJobRequest.jobId, cancelJobRequest.queueName)); | ||
} | ||
await validateAPIResponse(response, 204); | ||
}; |
{ | ||
"extends": "../../tsconfig-base.json", | ||
"compilerOptions": { | ||
"outDir": "./out", | ||
"rootDir": "src", | ||
"composite": true | ||
}, | ||
"references": [] | ||
} | ||
"extends": "../../tsconfig-base.json", | ||
"compilerOptions": { | ||
"outDir": "./out", | ||
"rootDir": "src", | ||
"composite": true | ||
}, | ||
"references": [ | ||
{ | ||
"path": "../forge-api" | ||
} | ||
] | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111955
73
1797
2
2
1
6
1
+ Addeduuid@^3.4.0
+ Added@forge/api@0.0.0-experimental-f85f9b1(transitive)
+ Added@forge/auth@0.0.1(transitive)
+ Added@forge/egress@1.1.2(transitive)
+ Added@forge/storage@0.0.0-experimental-f85f9b1(transitive)
+ Added@types/node@22.10.2(transitive)
+ Added@types/node-fetch@2.6.12(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addedform-data@4.0.1(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedminimatch@5.1.6(transitive)
+ Addednode-fetch@2.6.7(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedtslib@1.14.1(transitive)
+ Addedundici-types@6.20.0(transitive)
+ Addeduuid@3.4.0(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)