@barchart/common-node-js
Version:
Common classes, utilities, and functions for building Node.js servers
1,023 lines (834 loc) • 31.1 kB
JavaScript
const aws = require('aws-sdk'),
log4js = require('log4js');
const array = require('@barchart/common-js/lang/array'),
assert = require('@barchart/common-js/lang/assert'),
Disposable = require('@barchart/common-js/lang/Disposable'),
is = require('@barchart/common-js/lang/is'),
object = require('@barchart/common-js/lang/object'),
promise = require('@barchart/common-js/lang/promise'),
Scheduler = require('@barchart/common-js/timing/Scheduler');
module.exports = (() => {
'use strict';
const logger = log4js.getLogger('common-node/aws/SqsProvider');
/**
* A facade for Amazon's Simple Queue Service (SQS). The constructor
* accepts configuration options. The promise-based instance functions
* abstract knowledge of the AWS API.
*
* @public
* @extends Disposable
* @param {object} configuration
* @param {string} configuration.region - The AWS region (e.g. "us-east-1").
* @param {string} configuration.prefix - The prefix that is prepended to any queue name.
* @param {string=} configuration.apiVersion - The SES version (defaults to "2012-11-05").
*/
class SqsProvider extends Disposable {
constructor(configuration) {
super();
assert.argumentIsRequired(configuration, 'configuration', Object);
assert.argumentIsRequired(configuration.region, 'configuration.region', String);
assert.argumentIsRequired(configuration.prefix, 'configuration.prefix', String);
assert.argumentIsOptional(configuration.apiVersion, 'configuration.apiVersion', String);
this._configuration = configuration;
this._sqs = null;
this._scheduler = new Scheduler();
this._queueUrlPromises = {};
this._queueArnPromises = {};
this._queueObservers = {};
this._knownQueues = {};
this._startPromise = null;
this._started = false;
this._counter = 0;
}
/**
* Initializes the Amazon SDK. Call this before invoking any other instance
* functions.
*
* @public
* @async
* @returns {Promise<Boolean>}
*/
async start() {
if (this.getIsDisposed()) {
return Promise.reject('Unable to start, the SQS provider has been disposed.');
}
if (this._startPromise === null) {
this._startPromise = Promise.resolve()
.then(() => {
aws.config.update({region: this._configuration.region});
this._sqs = new aws.SQS({apiVersion: this._configuration.apiVersion || '2012-11-05'});
}).then(() => {
logger.info('The SQS provider has started');
this._started = true;
return this._started;
}).catch((e) => {
logger.error('The SQS provider failed to start', e);
throw e;
});
}
return this._startPromise;
}
/**
* Returns a clone of the configuration object originally passed
* to the constructor.
*
* @public
* @returns {Object}
*/
getConfiguration() {
if (this.getIsDisposed()) {
throw new Error('The SQS provider has been disposed.');
}
return object.clone(this._configuration);
}
/**
* Returns a list of queue URL's where the queue names start with a
* given prefix.
*
* @public
* @async
* @param {string=} queueNamePrefix - The prefix a queue name must have to be returned.
* @returns {Promise<String[]>}
*/
async getQueues(queueNamePrefix) {
return Promise.resolve()
.then(() => {
assert.argumentIsOptional(queueNamePrefix, 'queueNamePrefix', String);
return promise.build((resolveCallback, rejectCallback) => {
let queuePrefixToUse = this._configuration.prefix;
if (queueNamePrefix) {
queuePrefixToUse = queuePrefixToUse + queueNamePrefix;
}
logger.info('Listing queues with name prefix [', queuePrefixToUse, ']');
this._sqs.listQueues({ QueueNamePrefix: queuePrefixToUse }, (error, data) => {
if (error === null) {
const queueUrls = data.QueueUrls || [ ];
logger.debug('Listing of [', queueUrls.length, '] queues with name prefix [', queuePrefixToUse, '] complete');
resolveCallback(queueUrls);
} else {
logger.error('Listing of queues with name prefix [', queuePrefixToUse, '] failed');
logger.error(error);
rejectCallback('Failed to list queues.');
}
});
});
});
}
/**
* Given a queue's name, return the queue's URL. If no queue with the given
* name exists, it will be created.
*
* @public
* @async
* @param {string} queueName - The name of the queue to find.
* @param {Object=} createOptions - Options to use when queue does not exist and must be created.
* @returns {Promise<String>}
*/
async getQueueUrl(queueName, createOptions) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
if (!this._queueUrlPromises.hasOwnProperty(qualifiedQueueName)) {
logger.debug('The SQS provider has not cached the queue URL. Issuing request to create queue.');
let retentionTime = null;
if (createOptions && is.number(createOptions.retentionTime)) {
retentionTime = createOptions.retentionTime;
}
let tags = null;
if (createOptions && is.object(createOptions.tags)) {
tags = createOptions.tags;
}
this._queueUrlPromises[qualifiedQueueName] = this.createQueue(queueName, retentionTime, tags);
}
return this._queueUrlPromises[qualifiedQueueName];
});
}
/**
* Given a queue's name, return the queue's attributes.
*
* @public
* @async
* @param {string} queueUrl - The url of the queue to find.
* @param {array=} attributes - The names of attributes to return. By default set to 'All'.
* @returns {Promise<Object>}
*/
async getQueueAttributes(queueUrl, attributes) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueUrl, 'queueName', String);
if (attributes) {
assert.argumentIsArray(attributes, 'attributes');
}
checkReady.call(this);
const payload = { };
payload.QueueUrl = queueUrl;
if (!attributes || attributes.length === 0) {
payload.AttributeNames = [ 'All' ];
} else {
payload.AttributeNames = attributes;
}
return this._sqs.getQueueAttributes(payload).promise()
.then((data) => {
logger.info('Queue attribute lookup complete [', queueUrl, ']');
return data.Attributes;
}).catch((error) => {
logger.error('Queue attribute lookup failed [', queueUrl, ']');
logger.error(error);
throw error;
});
});
}
/**
* Given a queue's name, return Amazon's unique identifier for the queue
* (i.e. the ARN). If no queue with the given name exists, it will be created.
*
* @public
* @async
* @param {string} queueName - The name of the queue to find.
* @param {Object=} createOptions - Options to use when queue does not exist and must be created.
* @returns {Promise<String>}
*/
async getQueueArn(queueName, createOptions) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
if (!this._queueArnPromises.hasOwnProperty(qualifiedQueueName)) {
this._queueArnPromises[qualifiedQueueName] = this.getQueueUrl(queueName, createOptions)
.then((queueUrl) => {
return promise.build(
(resolveCallback, rejectCallback) => {
logger.debug('Getting queue attributes [', qualifiedQueueName, ']');
this._sqs.getQueueAttributes({
QueueUrl: queueUrl,
AttributeNames: ['QueueArn']
}, (error, data) => {
if (error === null) {
logger.info('Queue attribute lookup complete [', qualifiedQueueName, ']');
resolveCallback(data.Attributes.QueueArn);
} else {
logger.error('Queue attribute lookup failed [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to lookup ARN for queue.');
}
});
}
);
});
}
return this._queueArnPromises[qualifiedQueueName];
});
}
/**
* Creates a queue having the given name (and other options) and returns
* the queue's URL. If the queue already exists, the URL of the existing
* queue is returned.
*
* @public
* @async
* @param {string} queueName - The name of the queue to create.
* @param {Number=} retentionTime - The length of time a queue will retain a message in seconds.
* @param {Object=} tags - Tags to assign to the queue.
* @returns {Promise<String>}
*/
async createQueue(queueName, retentionTime, tags) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsOptional(retentionTime, 'retentionTime', Number);
assert.argumentIsOptional(tags, 'tags', Object);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
return promise.build(
(resolveCallback, rejectCallback) => {
logger.debug('Creating queue [', qualifiedQueueName, ']');
const payload = {
QueueName: qualifiedQueueName,
};
if (is.number(retentionTime)) {
payload.Attributes = {
MessageRetentionPeriod: retentionTime.toString()
};
}
if (is.object(tags)) {
const keys = object.keys(tags);
if (keys.length > 0) {
payload.tags = tags;
}
}
this._sqs.createQueue(payload, (error, data) => {
if (error === null) {
logger.info('Queue created [', qualifiedQueueName, ']');
const queueUrl = data.QueueUrl;
this._knownQueues[qualifiedQueueName] = queueUrl;
resolveCallback(queueUrl);
} else {
logger.error('Queue creation failed [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to create queue.');
}
});
}
);
});
}
/**
* Deletes a queue having the given name.
*
* @public
* @async
* @param {string} queueName - The name of the queue to delete.
* @returns {Promise}
*/
async deleteQueue(queueName) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
let deletePromise;
if (this._knownQueues.hasOwnProperty(qualifiedQueueName)) {
deletePromise = executeQueueDelete.call(this, qualifiedQueueName, this._knownQueues[qualifiedQueueName]);
} else {
deletePromise = this.getQueueUrl(queueName)
.then((queueUrl) => {
return executeQueueDelete.call(this, qualifiedQueueName, queueUrl);
});
}
return deletePromise;
});
}
/**
* Deletes a queue having the given URL.
*
* @public
* @async
* @param {string} queueUrl - The URL of the queue to delete.
* @returns {Promise}
*/
async deleteQueueUrl(queueUrl) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueUrl, 'queueUrl', String);
return executeQueueDelete.call(this, 'name not specified', queueUrl);
});
}
/**
* Enqueues a message in the queue. If the queue doesn't exist, it will
* be created.
*
* @public
* @async
* @param {string} queueName - The name of the queue to add the message to.
* @param {Object} payload - The message to enqueue (will be serialized to JSON).
* @param {Number=} delaySeconds - The number of seconds to prevent message from being retrieved from the queue.
* @param {Object=} createOptions - Options to use when queue does not exist and must be created.
* @returns {Promise}
*/
async send(queueName, payload, delaySeconds, createOptions) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsRequired(payload, 'payload', Object);
assert.argumentIsOptional(delaySeconds, 'delaySeconds', Number);
checkReady.call(this);
return this.getQueueUrl(queueName, createOptions)
.then((queueUrl) => {
return promise.build(
(resolveCallback, rejectCallback) => {
const counter = ++this._counter;
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
logger.debug('Sending message [', counter, '] to queue [', qualifiedQueueName, ']');
logger.trace(payload);
const message = { };
message.QueueUrl = queueUrl;
message.MessageBody = JSON.stringify(payload);
if (is.number(delaySeconds)) {
message.DelaySeconds = delaySeconds;
}
this._sqs.sendMessage(message, (error, data) => {
if (error === null) {
logger.info('Sent message [', counter, '] to queue [', qualifiedQueueName, ']');
resolveCallback();
} else {
logger.error('Queue send [', counter, '] failed:', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to send message to queue.');
}
});
}
);
});
});
}
/**
* Enqueues a batch of messages (up to 10) in the queue. If the queue doesn't exist, it will
* be created.
*
* @public
* @async
* @param {string} queueName - The name of the queue to add the message to.
* @param {Object[]} batch - The messages to enqueue (each will be serialized to JSON).
* @param {Object=} createOptions - Options to use when queue does not exist and must be created.
* @returns {Promise}
*/
async sendBatch(queueName, batch, createOptions) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsArray(batch, 'batch');
checkReady.call(this);
if (batch.length === 0) {
return Promise.resolve();
}
if (batch.length > 10) {
return Promise.reject('The SQS provider is unable to enqueue more than 10 messages at once.');
}
return this.getQueueUrl(queueName, createOptions)
.then((queueUrl) => {
return promise.build(
(resolveCallback, rejectCallback) => {
this._counter += batch.length;
const start = this._counter - batch.length + 1;
const end = this._counter;
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
logger.debug('Sending messages [', start, '] through [', end, '] to queue [', qualifiedQueueName, ']');
logger.trace(batch);
this._sqs.sendMessageBatch({
QueueUrl: queueUrl,
Entries: batch.map((item, i) => {
return {
Id: i.toString(),
MessageBody: JSON.stringify(item)
};
})
}, (error, data) => {
if (error === null) {
if (data.Failed.length !== 0) {
logger.error('Queue send [', start, '] through [', end, '] failed, [', data.Failed.length, '] messages could not be enqueued [', qualifiedQueueName, ']');
rejectCallback('Failed to send messages to queue.');
} else {
logger.info('Sent messages [', start, '] through [', end, '] to queue [', qualifiedQueueName, ']');
resolveCallback();
}
} else {
logger.error('Queue send [', start, '] through [', end, '] failed, [', batch.length, '] messages could not be enqueued [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to send messages to queue.');
}
});
}
);
});
});
}
/**
* Reads from the queue and deletes any messages that are read. After
* the operation, the queue will not necessarily be empty.
*
* @public
* @async
* @param {string} queueName - The name of the queue to read.
* @param {Number=} waitDuration - The maximum amount of time the server-side long-poll will wait for messages to become available.
* @param {Number=} maximumMessages - The maximum number of messages to read (cannot be more than 10).
* @param {Boolean=} synchronousDelete - If true, the promise won't resolve until new messages have been read *and deleted* from the queue.
* @returns {Promise<Object[]>}
*/
async receive(queueName, waitDuration, maximumMessages, synchronousDelete) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsOptional(waitDuration, 'waitDuration', Number);
assert.argumentIsOptional(maximumMessages, 'maximumMessages', Number);
assert.argumentIsOptional(synchronousDelete, 'synchronousDelete', Boolean);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
if (this._queueObservers.hasOwnProperty(qualifiedQueueName)) {
throw new Error('The queue is being observed.');
}
return receiveMessages.call(this, queueName, waitDuration, maximumMessages, synchronousDelete);
});
}
/**
* Reads all messages from queue (perhaps requiring multiple calls to the
* AWS SDK) and returns an array of messages (use with caution).
*
* @public
* @async
* @param {string} queueName - The name of the queue to read.
* @param {Function=} mapper - A function that can be used to map messages into something else.
* @param {Boolean=} synchronousDelete - If true, the promise won't resolve until new messages have been read *and deleted* from the queue.
* @param {Number=} maximumMessages - If positive, the maximum number of messages to read before stopping. This logic is approximate, you may receive a few more messages (up to ten more).
* @returns {Promise<Object[]>}
*/
async drain(queueName, mapper, synchronousDelete, maximumMessages) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsOptional(mapper, 'mapper', Function);
assert.argumentIsOptional(synchronousDelete, 'synchronousDelete', Boolean);
assert.argumentIsOptional(maximumMessages, 'maximumMessages', Number);
const mapperToUse = mapper || (m => m);
const batches = [ ];
const batchSize = 10;
let count = 0;
const executeDrain = () => {
return this.receive(queueName, 0, batchSize, synchronousDelete)
.then((messages) => {
batches.push(messages.map(mapperToUse));
count = count + messages.length;
if (messages.length === 0 || (is.positive(maximumMessages) && count >= maximumMessages)) {
return batches;
} else {
return executeDrain();
}
});
};
return executeDrain()
.then(() => {
return array.flatten(batches);
});
});
}
/**
* Purges all messages from an SQS queue.
*
* @public
* @async
* @param {string} queueName - The name of the queue to purge.
* @returns {Promise<Boolean>}
*/
async purge(queueName) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
checkReady.call(this);
return this.getQueueUrl(queueName)
.then((queueUrl) => {
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
logger.debug(`Queue purge beginning [ ${qualifiedQueueName} ]`);
return promise.build((resolveCallback, rejectCallback) => {
const payload = { };
payload.QueueUrl = queueUrl;
this._sqs.purgeQueue(payload, (error, data) => {
if (error === null) {
logger.info(`Queue purge complete [ ${qualifiedQueueName} ]`);
resolveCallback(true);
} else {
logger.error(`Queue purge failed [ ${qualifiedQueueName} ]`);
logger.error(error);
rejectCallback('Failed to purge queue');
}
});
});
});
});
}
/**
* Makes repeated reads from a queue until canceled and returns messages
* using the callback provided.
*
* @public
* @param {string} queueName - The name of the queue to read.
* @param {Function} callback - Invoked with a messages as they become available.
* @param {Number=} pollInterval - The milliseconds to wait between polling the queue.
* @param {Number=} pollDuration - The maximum amount of time the server-side long-poll will wait for messages to become available.
* @param {Number=} batchSize - The maximum number of messages to read per request (cannot be more than 10).
* @param {Object=} createOptions - Options to use when queue does not exist and must be created.
* @returns {Disposable}
*/
observe(queueName, callback, pollInterval, pollDuration, batchSize, createOptions) {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsRequired(callback, 'callback', Function);
assert.argumentIsOptional(pollInterval, 'pollInterval', Number);
assert.argumentIsOptional(pollDuration, 'pollDuration', Number);
assert.argumentIsOptional(batchSize, 'batchSize', Number);
checkReady.call(this);
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
if (this._queueObservers.hasOwnProperty(qualifiedQueueName)) {
throw new Error('The queue is already being observed.');
}
logger.debug('Creating observer for queue [', qualifiedQueueName, ']');
let disposed = false;
this._queueObservers[qualifiedQueueName] = Disposable.fromAction(() => {
logger.info('Disposing observer of queue [', qualifiedQueueName, ']');
disposed = true;
delete this._queueObservers[qualifiedQueueName];
});
const checkQueue = () => {
if (disposed) {
return;
}
let delay;
receiveMessages.call(this, queueName, pollDuration, batchSize, false, createOptions)
.then((messages) => {
return Promise.all(messages.map((message) => {
if (disposed) {
return;
}
return callback(message);
})).catch((error) => {
logger.error('An error occurred while processing message(s) from queue [', qualifiedQueueName, ']');
logger.error(error);
}).then(() => {
if (messages.length === 0) {
delay = pollInterval || 2000;
} else {
delay = 0;
}
});
}).catch((error) => {
logger.error('An error occurred while receiving message(s) from queue [', qualifiedQueueName, ']');
logger.error(error);
}).then(() => {
if (disposed) {
return;
}
if (!is.number(delay)) {
delay = 5000;
}
this._scheduler.schedule(checkQueue, delay, 'Check queue (' + qualifiedQueueName + ')');
});
};
checkQueue();
return this._queueObservers[qualifiedQueueName];
}
/**
* Changes the policy on a Queue. The "policy" must conform to Amazon's schema.
*
* @public
* @async
* @param {string} queueName - The name of the queue to adjust.
* @param {Object} policy - The Amazon schema-compliant policy.
* @returns {Promise}
*/
async setQueuePolicy(queueName, policy) {
return Promise.resolve()
.then(() => {
assert.argumentIsRequired(queueName, 'queueName', String);
assert.argumentIsRequired(policy, 'policy', Object);
checkReady.call(this);
return this.getQueueUrl(queueName)
.then((queueUrl) => {
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
logger.debug('Updating queue policy [', qualifiedQueueName, ']');
logger.trace(policy);
return promise.build((resolveCallback, rejectCallback) => {
this._sqs.setQueueAttributes({
QueueUrl: queueUrl,
Attributes: {
Policy: JSON.stringify(policy)
}
}, (error, data) => {
if (error === null) {
logger.info('Queue policy updated for [', qualifiedQueueName, ']');
resolveCallback();
} else {
logger.error('Queue policy update failed [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to update queue policy.');
}
});
});
});
});
}
_onDispose() {
Object.keys(this._queueObservers).forEach((key) => {
this._queueObservers[key].dispose();
});
this._scheduler.dispose();
this._scheduler = null;
this._queueUrlPromises = null;
this._queueArnPromises = null;
this._queueObservers = null;
}
static getPolicyForSnsDelivery(queueArn, topicArn) {
const currentDate = new Date();
return {
Version: "2008-10-17",
Id: queueArn + "/SQSDefaultPolicy",
Statement: [{
Sid: "Sid" + currentDate.getTime(),
Effect: "Allow",
Principal: {
AWS: "*"
},
Action: "SQS:SendMessage",
Resource: queueArn,
Condition: {
ArnEquals: {
"aws:SourceArn": topicArn
}
}
}]
};
}
toString() {
return '[SqsProvider]';
}
}
async function receiveMessages(queueName, waitTime, maximumMessages, synchronousDelete, createOptions) {
let waitTimeToUse;
if (is.number(waitTime)) {
if (waitTime === 0) {
waitTimeToUse = 0;
} else {
waitTimeToUse = Math.round(waitTime / 1000);
}
} else {
waitTimeToUse = 20;
}
let maximumMessagesToUse;
if (is.number(maximumMessages)) {
maximumMessagesToUse = Math.max(Math.min(10, maximumMessages), 1);
} else {
maximumMessagesToUse = 1;
}
return this.getQueueUrl(queueName, createOptions)
.then((queueUrl) => {
return promise.build(
(resolveCallback, rejectCallback) => {
const qualifiedQueueName = getQualifiedQueueName(this._configuration.prefix, queueName);
logger.debug('Receiving message(s) from queue [', qualifiedQueueName, ']');
this._sqs.receiveMessage({
QueueUrl: queueUrl,
MaxNumberOfMessages: maximumMessagesToUse,
WaitTimeSeconds: waitTimeToUse
}, (error, data) => {
if (error === null) {
const messagesExist = is.array(data.Messages) && data.Messages.length !== 0;
if (messagesExist) {
logger.info('Received [', data.Messages.length, '] message(s) from queue [', qualifiedQueueName, ']');
logger.trace(data.Messages);
} else {
logger.debug('Received [ 0 ] message(s) from queue [', qualifiedQueueName, ']');
}
let messages;
try {
messages = (data.Messages || []).map((message) => {
return JSON.parse(message.Body);
});
} catch (parseError) {
logger.error('Failed to parse message(s) received from queue.', parseError);
messages = null;
}
let deletePromise;
if (messagesExist) {
deletePromise = deleteMessages.call(this, qualifiedQueueName, queueUrl, data.Messages);
if (!synchronousDelete) {
deletePromise = Promise.resolve();
}
} else {
deletePromise = Promise.resolve();
}
deletePromise.then(() => {
if (messages) {
resolveCallback(messages);
} else {
rejectCallback('Failed to parse message(s) received from queue.');
}
});
} else {
logger.error('SQS receive messages failed [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to receive messages from queue.');
}
});
}
);
});
}
async function deleteMessages(qualifiedQueueName, queueUrl, messages) {
const messageCount = messages.length;
if (messageCount === 0) {
return Promise.resolve();
}
return promise.build(
(resolveCallback, rejectCallback) => {
logger.debug('Deleting [', messageCount, '] message(s) from queue [', qualifiedQueueName, ']');
this._sqs.deleteMessageBatch({
QueueUrl: queueUrl,
Entries: messages.map((message, index) => {
return {
Id: index.toString(),
ReceiptHandle: message.ReceiptHandle
};
})
}, (error, data) => {
if (error === null) {
let deletedCount;
if (is.array(data.Failed)) {
deletedCount = messageCount - data.Failed.length;
} else {
deletedCount = messageCount;
}
logger.info('Deleted [', deletedCount, '] message(s) from queue [', qualifiedQueueName, ']');
if (deletedCount !== messageCount) {
logger.warn('Failed to delete [', data.Failed.length, '] message(s) from queue [', qualifiedQueueName, ']');
rejectCallback('Failed to delete some messages from queue.');
} else {
resolveCallback();
}
} else {
logger.error('SQS message delete failed [', qualifiedQueueName, ']');
logger.error(error);
rejectCallback('Failed to delete messages from queue.');
}
});
}
);
}
async function executeQueueDelete(qualifiedQueueName, queueUrl) {
return promise.build(
(resolveCallback, rejectCallback) => {
logger.debug('Deleting queue [', qualifiedQueueName, '] at URL [', queueUrl, ']');
this._sqs.deleteQueue({
QueueUrl: queueUrl
}, (error, data) => {
if (error === null) {
logger.info('Queue deleted [', qualifiedQueueName, '] at URL [', queueUrl, ']');
resolveCallback();
} else {
logger.error('Queue delete failed [', qualifiedQueueName, '] at URL [', queueUrl, ']');
logger.error(error);
rejectCallback('Failed to delete queue.');
}
});
}
);
}
function checkReady() {
if (this.getIsDisposed()) {
throw new Error('The SQS provider has been disposed.');
}
if (!this._started) {
throw new Error('The SQS provider has not been started.');
}
}
const finalStarRegex = new RegExp('(\\*)$');
const finalHatRegex = new RegExp('(\\^)$');
const finalDotRegex = new RegExp('(\\.)$');
const finalDollarRegex = new RegExp('(\\$)$');
const starRegex = new RegExp('\\*', 'g');
const hatRegex = new RegExp('\\^', 'g');
const dotRegex = new RegExp('\\*', 'g');
const dollarRegex = new RegExp('\\*', 'g');
function getQualifiedQueueName(prefix, queueName) {
return sanitizedName(prefix + '-' + queueName);
}
function sanitizedName(messageType) {
return messageType.replace(finalStarRegex, '_star')
.replace(finalHatRegex, '_hat')
.replace(finalDotRegex, '_dot')
.replace(finalDollarRegex, '_dollar')
.replace(starRegex, '_star_')
.replace(hatRegex, '_hat_')
.replace(dotRegex, '_dot_')
.replace(dollarRegex, '_dollar_');
}
return SqsProvider;
})();