tyo-mq
Version:
Distributed Message Pub/Sub Service with socket.io
143 lines (124 loc) • 4.27 kB
JavaScript
/**
* In-process durable-message store.
*
* This store survives disconnects but not process restarts. It implements the
* Phase 2 Store interface and is intentionally small so custom production
* stores can mirror the same behavior.
*/
;
const crypto = require('crypto');
function MemoryStore(options) {
options = options || {};
var defaultTtl = options.default_ttl;
if (defaultTtl === undefined)
defaultTtl = options.defaultTtl;
if (defaultTtl === undefined)
defaultTtl = 24 * 60 * 60;
this.defaultTtl = Number(defaultTtl);
this.messages = [];
this.dlq = [];
}
MemoryStore.prototype._now = function () {
return Date.now();
};
MemoryStore.prototype._expiresAt = function (message) {
var ttl = message && message.ttl;
if (ttl === undefined || ttl === null)
ttl = this.defaultTtl;
ttl = Number(ttl);
if (!Number.isFinite(ttl) || ttl < 0)
return null;
return this._now() + ttl * 1000;
};
MemoryStore.prototype._purgeExpired = function () {
var now = this._now();
this.messages = this.messages.filter(function (entry) {
return !entry.expires_at || entry.expires_at > now;
});
};
MemoryStore.prototype.enqueue = function (realm, event, message) {
message = message || {};
this._purgeExpired();
var id = message.id || ('msg-' + this._now().toString(36) + '-' + crypto.randomBytes(6).toString('hex'));
this.messages.push({
id: id,
realm: String(realm || 'default'),
event: String(event || ''),
consumer: message.consumer || message.consumer_id || null,
payload: message.payload !== undefined ? message.payload : message.message,
producer: message.producer || null,
created_at: new Date(this._now()).toISOString(),
expires_at: this._expiresAt(message)
});
return Promise.resolve(id);
};
MemoryStore.prototype.dequeue = function (realm, event, consumer) {
this._purgeExpired();
realm = String(realm || 'default');
event = String(event || '');
consumer = String(consumer || '');
var result = this.messages.filter(function (entry) {
return entry.realm === realm && entry.event === event && String(entry.consumer || '') === consumer;
}).map(function (entry) {
return {
id: entry.id,
realm: entry.realm,
event: entry.event,
consumer: entry.consumer,
message: entry.payload,
producer: entry.producer,
created_at: entry.created_at,
expires_at: entry.expires_at
};
});
return Promise.resolve(result);
};
MemoryStore.prototype.ack = function (msgId) {
this.messages = this.messages.filter(function (entry) {
return entry.id !== msgId;
});
return Promise.resolve();
};
MemoryStore.prototype.deadLetter = function (msgId, reason) {
var found = null;
this.messages = this.messages.filter(function (entry) {
if (entry.id === msgId) {
found = entry;
return false;
}
return true;
});
if (found) {
this.dlq.push(Object.assign({}, found, {
reason: reason || null,
dead_lettered_at: new Date(this._now()).toISOString()
}));
}
return Promise.resolve(found ? found.id : null);
};
MemoryStore.prototype.listDlq = function (realm) {
var targetRealm = realm === undefined || realm === null ? null : String(realm);
return Promise.resolve(this.dlq.filter(function (entry) {
return !targetRealm || entry.realm === targetRealm;
}).map(function (entry) {
return {
id: entry.id,
realm: entry.realm,
event: entry.event,
consumer: entry.consumer,
message: entry.payload,
producer: entry.producer,
created_at: entry.created_at,
expires_at: entry.expires_at,
reason: entry.reason,
dead_lettered_at: entry.dead_lettered_at
};
}));
};
MemoryStore.prototype.discardDlq = function (msgId) {
this.dlq = this.dlq.filter(function (entry) {
return entry.id !== msgId;
});
return Promise.resolve();
};
module.exports = MemoryStore;