@forge/events
Advanced tools
Comparing version 0.2.0-next.0 to 0.2.0-next.1
# @forge/events | ||
## 0.2.0-next.1 | ||
### Minor Changes | ||
- a056b40: Add Queue.getStats() | ||
## 0.2.0-next.0 | ||
@@ -4,0 +10,0 @@ |
@@ -29,2 +29,5 @@ import { FailedEvent } from './types'; | ||
} | ||
export declare class JobDoesNotExistError 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 = exports.InvalidPushSettingsError = void 0; | ||
exports.JobDoesNotExistError = exports.InternalServerError = exports.PartialSuccessError = exports.RateLimitError = exports.NoEventsToPushError = exports.PayloadTooBigError = exports.TooManyEventsError = exports.InvalidQueueNameError = exports.InvalidPushSettingsError = void 0; | ||
class InvalidPushSettingsError extends Error { | ||
@@ -55,1 +55,7 @@ constructor(message) { | ||
exports.InternalServerError = InternalServerError; | ||
class JobDoesNotExistError extends Error { | ||
constructor(message) { | ||
super(message); | ||
} | ||
} | ||
exports.JobDoesNotExistError = JobDoesNotExistError; |
export { Queue } from './queue'; | ||
export { InvalidQueueNameError, TooManyEventsError, PayloadTooBigError, NoEventsToPushError, RateLimitError, PartialSuccessError, InternalServerError } from './errors'; | ||
export { InvalidQueueNameError, TooManyEventsError, PayloadTooBigError, NoEventsToPushError, RateLimitError, PartialSuccessError, InternalServerError, JobDoesNotExistError } from './errors'; | ||
export { JobProgress } from './jobProgress'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -13,1 +13,4 @@ "use strict"; | ||
Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return errors_1.InternalServerError; } }); | ||
Object.defineProperty(exports, "JobDoesNotExistError", { enumerable: true, get: function () { return errors_1.JobDoesNotExistError; } }); | ||
var jobProgress_1 = require("./jobProgress"); | ||
Object.defineProperty(exports, "JobProgress", { enumerable: true, get: function () { return jobProgress_1.JobProgress; } }); |
@@ -1,4 +0,5 @@ | ||
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 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.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.post = async (endpoint, body, apiClient) => { | ||
const request = { | ||
method: 'POST', | ||
body: JSON.stringify(body), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
return await apiClient(endpoint, request); | ||
}; |
@@ -1,2 +0,3 @@ | ||
import { APIResponse, FetchMethod, Payload, QueueParams, PushSettings } 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[], pushSettings?: PushSettings): 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"); | ||
class Queue { | ||
constructor(queueParams, apiClient) { | ||
validators_1.validateQueueName(queueParams.key); | ||
validators_1.validateQueueKey(queueParams.key); | ||
this.queueParams = queueParams; | ||
@@ -18,7 +17,11 @@ this.apiClient = apiClient || global.api.asApp().__requestAtlassian; | ||
validators_1.validatePayloads(payloads); | ||
const queryParams = { | ||
const queueName = this.queueParams.key; | ||
const jobId = 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.key, | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
type: 'avi:forge:app:event' | ||
time: new Date().toISOString() | ||
}; | ||
@@ -28,22 +31,13 @@ if (pushSettings) { | ||
if (pushSettings.delayInSeconds) { | ||
queryParams.delayInSeconds = pushSettings.delayInSeconds; | ||
pushRequest.delayInSeconds = pushSettings.delayInSeconds; | ||
} | ||
} | ||
const requestBody = queries_1.getPushBody(queryParams); | ||
return this.query(queries_1.PUSH_PATH, requestBody); | ||
const response = await queries_1.post(queries_1.PUSH_PATH, pushRequest, this.apiClient); | ||
await 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; |
@@ -13,5 +13,4 @@ import { RequestInit, Response } from 'node-fetch'; | ||
} | ||
export interface PushBodyParams { | ||
export interface PushRequest extends APIRequest { | ||
payload: Payload[]; | ||
queueName: string; | ||
schema: string; | ||
@@ -21,3 +20,5 @@ type: string; | ||
} | ||
export interface APIRequest extends PushBodyParams { | ||
export interface APIRequest { | ||
queueName: string; | ||
jobId: string; | ||
time: string; | ||
@@ -29,2 +30,3 @@ } | ||
} | ||
export declare type GetStatsRequest = APIRequest; | ||
//# sourceMappingURL=types.d.ts.map |
@@ -1,6 +0,8 @@ | ||
import { APIResponse, Payload, APIRequest, PushSettings } from './types'; | ||
export declare const validateQueueName: (queueName: string) => void; | ||
import { APIResponse, GetStatsRequest, Payload, PushRequest, PushSettings } from './types'; | ||
export declare const validateQueueKey: (queueName: string) => void; | ||
export declare const validatePushSettings: (settings: PushSettings) => void; | ||
export declare const validatePayloads: (payloads: Payload | Payload[]) => void; | ||
export declare const validateAPIResponse: (response: APIResponse, requestBody: APIRequest) => Promise<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>; | ||
//# sourceMappingURL=validators.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.validateAPIResponse = exports.validatePayloads = exports.validatePushSettings = exports.validateQueueName = void 0; | ||
exports.validateGetStatsAPIResponse = exports.validatePushAPIResponse = exports.validateAPIResponse = exports.validatePayloads = exports.validatePushSettings = exports.validateQueueKey = void 0; | ||
const errors_1 = require("./errors"); | ||
@@ -8,5 +8,5 @@ const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/; | ||
const MAXIMUM_PAYLOAD_SIZE_KB = 200; | ||
exports.validateQueueName = (queueName) => { | ||
exports.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('Queue names can only contain alphanumeric characters and the dash and underscore characters.'); | ||
} | ||
@@ -31,6 +31,13 @@ }; | ||
}; | ||
exports.validateAPIResponse = async (response, requestBody) => { | ||
exports.validateAPIResponse = async (response, expectedSuccessStatus) => { | ||
if (response.status === 429) { | ||
throw new errors_1.RateLimitError(`Too many requests`); | ||
} | ||
if (response.status != expectedSuccessStatus && 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); | ||
} | ||
}; | ||
exports.validatePushAPIResponse = async (response, requestBody) => { | ||
if (response.status === 413) { | ||
@@ -42,4 +49,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 { | ||
@@ -49,13 +59,19 @@ 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 exports.validateAPIResponse(response, 201); | ||
}; | ||
exports.validateGetStatsAPIResponse = async (response, getStatsRequest) => { | ||
if (response.status === 404) { | ||
throw new errors_1.JobDoesNotExistError(`The job ${getStatsRequest.jobId} doesn't exist for the queue ${getStatsRequest.queueName}`); | ||
} | ||
await exports.validateAPIResponse(response, 200); | ||
}; |
{ | ||
"name": "@forge/events", | ||
"version": "0.2.0-next.0", | ||
"version": "0.2.0-next.1", | ||
"description": "Forge Async Event methods", | ||
@@ -5,0 +5,0 @@ "author": "Atlassian", |
@@ -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 |
@@ -58,1 +58,7 @@ import { FailedEvent } from './types'; | ||
} | ||
export class JobDoesNotExistError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
} | ||
} |
@@ -9,3 +9,5 @@ export { Queue } from './queue'; | ||
PartialSuccessError, | ||
InternalServerError | ||
InternalServerError, | ||
JobDoesNotExistError | ||
} from './errors'; | ||
export { JobProgress } from './jobProgress'; |
@@ -1,11 +0,16 @@ | ||
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 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,7 @@ | ||
import { getPushBody, PUSH_PATH } from './queries'; | ||
import { validateAPIResponse, validatePayloads, validateQueueName, validatePushSettings } from './validators'; | ||
import { APIResponse, FetchMethod, Payload, PushBodyParams, QueueParams, APIRequest, PushSettings } from './types'; | ||
import { PUSH_PATH, post } from './queries'; | ||
import { validatePushAPIResponse, validatePayloads, validateQueueKey, validatePushSettings } from './validators'; | ||
import { FetchMethod, Payload, QueueParams, PushSettings, PushRequest } from './types'; | ||
import uuid from 'uuid/v4'; | ||
import { JobProgress } from './jobProgress'; | ||
async function getResponseBody(response: APIResponse, body: any): Promise<APIResponse> { | ||
await validateAPIResponse(response, body); | ||
return response; | ||
} | ||
export class Queue { | ||
@@ -15,3 +12,3 @@ private readonly apiClient: FetchMethod; | ||
constructor(queueParams: QueueParams, apiClient?: FetchMethod) { | ||
validateQueueName(queueParams.key); | ||
validateQueueKey(queueParams.key); | ||
this.queueParams = queueParams; | ||
@@ -21,10 +18,14 @@ this.apiClient = apiClient || (global as any).api.asApp().__requestAtlassian; | ||
async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<APIResponse> { | ||
async push(payloads: Payload | Payload[], pushSettings?: PushSettings): Promise<string> { | ||
validatePayloads(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.key, | ||
schema: 'ari:cloud:ecosystem::forge/app-event', | ||
type: 'avi:forge:app:event' | ||
time: new Date().toISOString() | ||
}; | ||
@@ -35,24 +36,14 @@ | ||
if (pushSettings.delayInSeconds) { | ||
queryParams.delayInSeconds = pushSettings.delayInSeconds; | ||
pushRequest.delayInSeconds = pushSettings.delayInSeconds; | ||
} | ||
} | ||
const requestBody = getPushBody(queryParams); | ||
return this.query(PUSH_PATH, requestBody); | ||
const response = await post(PUSH_PATH, pushRequest, this.apiClient); | ||
await validatePushAPIResponse(response, pushRequest); | ||
return `${queueName}#${jobId}`; | ||
} | ||
private async query(endpoint: string, body: APIRequest): Promise<APIResponse> { | ||
const response = await this.apiClient(endpoint, this.buildRequest(body)); | ||
return await getResponseBody(response, body); | ||
getJob(jobId: string): JobProgress { | ||
return new JobProgress(jobId, this.apiClient); | ||
} | ||
private buildRequest(requestBody: APIRequest) { | ||
return { | ||
method: 'POST', | ||
body: JSON.stringify(requestBody), | ||
headers: { | ||
'content-type': 'application/json' | ||
} | ||
}; | ||
} | ||
} |
@@ -14,5 +14,4 @@ import { RequestInit, Response } from 'node-fetch'; | ||
export interface PushBodyParams { | ||
export interface PushRequest extends APIRequest { | ||
payload: Payload[]; | ||
queueName: string; | ||
schema: string; | ||
@@ -23,3 +22,5 @@ type: string; | ||
export interface APIRequest extends PushBodyParams { | ||
export interface APIRequest { | ||
queueName: string; | ||
jobId: string; | ||
time: string; | ||
@@ -32,1 +33,3 @@ } | ||
} | ||
export type GetStatsRequest = APIRequest; |
import { | ||
InternalServerError, | ||
InvalidQueueNameError, | ||
JobDoesNotExistError, | ||
NoEventsToPushError, | ||
@@ -11,3 +12,3 @@ PartialSuccessError, | ||
} from './errors'; | ||
import { APIResponse, Payload, APIRequest, PushSettings } from './types'; | ||
import { APIResponse, GetStatsRequest, Payload, PushRequest, PushSettings } from './types'; | ||
@@ -18,5 +19,7 @@ const VALID_QUEUE_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/; | ||
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( | ||
'Queue names can only contain alphanumeric characters and the dash and underscore characters.' | ||
); | ||
} | ||
@@ -46,3 +49,3 @@ }; | ||
export const validateAPIResponse = async (response: APIResponse, requestBody: APIRequest) => { | ||
export const validateAPIResponse = async (response: APIResponse, expectedSuccessStatus: number) => { | ||
if (response.status === 429) { | ||
@@ -52,2 +55,15 @@ throw new RateLimitError(`Too many requests`); | ||
if (response.status != expectedSuccessStatus && 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 | ||
); | ||
} | ||
}; | ||
export const validatePushAPIResponse = async (response: APIResponse, requestBody: PushRequest) => { | ||
if (response.status === 413) { | ||
@@ -61,27 +77,37 @@ //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( | ||
`The job ${getStatsRequest.jobId} doesn't exist for the queue ${getStatsRequest.queueName}` | ||
); | ||
} | ||
await validateAPIResponse(response, 200); | ||
}; |
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
106788
45
1160
1