Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement →
Sign In

cluster-shared-cache

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cluster-shared-cache - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2
+375
index.js
const cluster = require('cluster');
const EventEmitter = require('events');
class ClusterSharedCache extends EventEmitter {
constructor(options = {}) {
super();
this.options = {
maxSize: options.maxSize || 1000,
ttl: options.ttl || 300000,
checkPeriod: options.checkPeriod || 60000,
...options
};
this.cache = new Map();
this.timers = new Map();
this.setupIPC();
this.startCleanup();
}
setupIPC() {
if (cluster.isMaster) {
this.masterCache = new Map();
this.masterTimers = new Map();
cluster.on('message', (worker, message) => {
if (message.type && message.type.startsWith('CACHE_')) {
this.handleMasterMessage(worker, message);
}
});
this.on('masterCacheUpdate', (data) => {
Object.values(cluster.workers).forEach(worker => {
if (worker && worker.send) {
worker.send(data);
}
});
});
} else {
process.on('message', (message) => {
if (message.type && message.type.startsWith('CACHE_')) {
this.handleWorkerMessage(message);
}
});
}
}
handleMasterMessage(worker, message) {
const { type, key, value, ttl, requestId } = message;
switch (type) {
case 'CACHE_SET':
this.masterSet(key, value, ttl);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_SET',
key,
value,
ttl,
timestamp: Date.now()
});
break;
case 'CACHE_GET':
const result = this.masterGet(key);
worker.send({
type: 'CACHE_GET_RESPONSE',
requestId,
key,
value: result
});
break;
case 'CACHE_DELETE':
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
break;
case 'CACHE_CLEAR':
this.masterClear();
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_CLEAR'
});
break;
}
}
handleWorkerMessage(message) {
const { type, key, value, ttl, timestamp, requestId } = message;
switch (type) {
case 'CACHE_SYNC_SET':
this.localSet(key, value, ttl, timestamp);
break;
case 'CACHE_SYNC_DELETE':
this.localDelete(key);
break;
case 'CACHE_SYNC_CLEAR':
this.localClear();
break;
case 'CACHE_GET_RESPONSE':
this.emit(`get_response_${requestId}`, value);
break;
}
}
// Master cache operations
masterSet(key, value, ttl) {
if (this.masterCache.size >= this.options.maxSize) {
const firstKey = this.masterCache.keys().next().value;
this.masterDelete(firstKey);
}
const expiresAt = ttl ? Date.now() + ttl : null;
this.masterCache.set(key, { value, expiresAt });
if (ttl) {
if (this.masterTimers.has(key)) {
clearTimeout(this.masterTimers.get(key));
}
const timer = setTimeout(() => {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
}, ttl);
this.masterTimers.set(key, timer);
}
}
masterGet(key) {
const item = this.masterCache.get(key);
if (!item) return undefined;
if (item.expiresAt && Date.now() > item.expiresAt) {
this.masterDelete(key);
return undefined;
}
return item.value;
}
masterDelete(key) {
this.masterCache.delete(key);
if (this.masterTimers.has(key)) {
clearTimeout(this.masterTimers.get(key));
this.masterTimers.delete(key);
}
}
masterClear() {
this.masterCache.clear();
this.masterTimers.forEach(timer => clearTimeout(timer));
this.masterTimers.clear();
}
localSet(key, value, ttl, timestamp) {
if (this.cache.size >= this.options.maxSize) {
const firstKey = this.cache.keys().next().value;
this.localDelete(firstKey);
}
const expiresAt = ttl ? (timestamp || Date.now()) + ttl : null;
this.cache.set(key, { value, expiresAt });
if (ttl) {
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
}
const remainingTtl = expiresAt - Date.now();
if (remainingTtl > 0) {
const timer = setTimeout(() => {
this.localDelete(key);
}, remainingTtl);
this.timers.set(key, timer);
}
}
}
localDelete(key) {
this.cache.delete(key);
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
}
localClear() {
this.cache.clear();
this.timers.forEach(timer => clearTimeout(timer));
this.timers.clear();
}
async set(key, value, ttl = this.options.ttl) {
if (cluster.isMaster) {
this.masterSet(key, value, ttl);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_SET',
key,
value,
ttl,
timestamp: Date.now()
});
} else {
process.send({
type: 'CACHE_SET',
key,
value,
ttl
});
}
}
async get(key) {
if (cluster.isMaster) {
return this.masterGet(key);
} else {
const localItem = this.cache.get(key);
if (localItem) {
if (localItem.expiresAt && Date.now() > localItem.expiresAt) {
this.localDelete(key);
} else {
return localItem.value;
}
}
const requestId = `${Date.now()}_${Math.random()}`;
return new Promise((resolve) => {
const timeout = setTimeout(() => {
this.removeListener(`get_response_${requestId}`, responseHandler);
resolve(undefined);
}, 5000);
const responseHandler = (value) => {
clearTimeout(timeout);
resolve(value);
};
this.once(`get_response_${requestId}`, responseHandler);
process.send({
type: 'CACHE_GET',
key,
requestId
});
});
}
}
async delete(key) {
if (cluster.isMaster) {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
} else {
process.send({
type: 'CACHE_DELETE',
key
});
}
}
async clear() {
if (cluster.isMaster) {
this.masterClear();
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_CLEAR'
});
} else {
process.send({
type: 'CACHE_CLEAR'
});
}
}
has(key) {
const item = this.cache.get(key);
if (!item) return false;
if (item.expiresAt && Date.now() > item.expiresAt) {
this.localDelete(key);
return false;
}
return true;
}
size() {
return this.cache.size;
}
keys() {
return Array.from(this.cache.keys());
}
values() {
return Array.from(this.cache.values()).map(item => item.value);
}
entries() {
return Array.from(this.cache.entries()).map(([key, item]) => [key, item.value]);
}
startCleanup() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.cleanupInterval = setInterval(() => {
const now = Date.now();
const expiredKeys = [];
for (const [key, item] of this.cache.entries()) {
if (item.expiresAt && now > item.expiresAt) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => this.localDelete(key));
// Master cleanup
if (cluster.isMaster && this.masterCache) {
const expiredMasterKeys = [];
for (const [key, item] of this.masterCache.entries()) {
if (item.expiresAt && now > item.expiresAt) {
expiredMasterKeys.push(key);
}
}
expiredMasterKeys.forEach(key => {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
});
}
}, this.options.checkPeriod);
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.timers.forEach(timer => clearTimeout(timer));
this.timers.clear();
if (this.masterTimers) {
this.masterTimers.forEach(timer => clearTimeout(timer));
this.masterTimers.clear();
}
this.removeAllListeners();
}
}
module.exports = ClusterSharedCache;
+1
-1

@@ -1,2 +0,2 @@

const ClusterSharedCache = require('../src/index');
const ClusterSharedCache = require('../index');
const cluster = require('cluster');

@@ -3,0 +3,0 @@

{
"name": "cluster-shared-cache",
"version": "1.0.1",
"version": "1.0.2",
"description": "Handling shared in memory cache in node js cluster module",

@@ -5,0 +5,0 @@ "main": "index.js",

const cluster = require('cluster');
const EventEmitter = require('events');
class ClusterSharedCache extends EventEmitter {
constructor(options = {}) {
super();
this.options = {
maxSize: options.maxSize || 1000,
ttl: options.ttl || 300000,
checkPeriod: options.checkPeriod || 60000,
...options
};
this.cache = new Map();
this.timers = new Map();
this.setupIPC();
this.startCleanup();
}
setupIPC() {
if (cluster.isMaster) {
this.masterCache = new Map();
this.masterTimers = new Map();
cluster.on('message', (worker, message) => {
if (message.type && message.type.startsWith('CACHE_')) {
this.handleMasterMessage(worker, message);
}
});
this.on('masterCacheUpdate', (data) => {
Object.values(cluster.workers).forEach(worker => {
if (worker && worker.send) {
worker.send(data);
}
});
});
} else {
process.on('message', (message) => {
if (message.type && message.type.startsWith('CACHE_')) {
this.handleWorkerMessage(message);
}
});
}
}
handleMasterMessage(worker, message) {
const { type, key, value, ttl, requestId } = message;
switch (type) {
case 'CACHE_SET':
this.masterSet(key, value, ttl);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_SET',
key,
value,
ttl,
timestamp: Date.now()
});
break;
case 'CACHE_GET':
const result = this.masterGet(key);
worker.send({
type: 'CACHE_GET_RESPONSE',
requestId,
key,
value: result
});
break;
case 'CACHE_DELETE':
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
break;
case 'CACHE_CLEAR':
this.masterClear();
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_CLEAR'
});
break;
}
}
handleWorkerMessage(message) {
const { type, key, value, ttl, timestamp, requestId } = message;
switch (type) {
case 'CACHE_SYNC_SET':
this.localSet(key, value, ttl, timestamp);
break;
case 'CACHE_SYNC_DELETE':
this.localDelete(key);
break;
case 'CACHE_SYNC_CLEAR':
this.localClear();
break;
case 'CACHE_GET_RESPONSE':
this.emit(`get_response_${requestId}`, value);
break;
}
}
// Master cache operations
masterSet(key, value, ttl) {
if (this.masterCache.size >= this.options.maxSize) {
const firstKey = this.masterCache.keys().next().value;
this.masterDelete(firstKey);
}
const expiresAt = ttl ? Date.now() + ttl : null;
this.masterCache.set(key, { value, expiresAt });
if (ttl) {
if (this.masterTimers.has(key)) {
clearTimeout(this.masterTimers.get(key));
}
const timer = setTimeout(() => {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
}, ttl);
this.masterTimers.set(key, timer);
}
}
masterGet(key) {
const item = this.masterCache.get(key);
if (!item) return undefined;
if (item.expiresAt && Date.now() > item.expiresAt) {
this.masterDelete(key);
return undefined;
}
return item.value;
}
masterDelete(key) {
this.masterCache.delete(key);
if (this.masterTimers.has(key)) {
clearTimeout(this.masterTimers.get(key));
this.masterTimers.delete(key);
}
}
masterClear() {
this.masterCache.clear();
this.masterTimers.forEach(timer => clearTimeout(timer));
this.masterTimers.clear();
}
localSet(key, value, ttl, timestamp) {
if (this.cache.size >= this.options.maxSize) {
const firstKey = this.cache.keys().next().value;
this.localDelete(firstKey);
}
const expiresAt = ttl ? (timestamp || Date.now()) + ttl : null;
this.cache.set(key, { value, expiresAt });
if (ttl) {
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
}
const remainingTtl = expiresAt - Date.now();
if (remainingTtl > 0) {
const timer = setTimeout(() => {
this.localDelete(key);
}, remainingTtl);
this.timers.set(key, timer);
}
}
}
localDelete(key) {
this.cache.delete(key);
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
}
localClear() {
this.cache.clear();
this.timers.forEach(timer => clearTimeout(timer));
this.timers.clear();
}
async set(key, value, ttl = this.options.ttl) {
if (cluster.isMaster) {
this.masterSet(key, value, ttl);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_SET',
key,
value,
ttl,
timestamp: Date.now()
});
} else {
process.send({
type: 'CACHE_SET',
key,
value,
ttl
});
}
}
async get(key) {
if (cluster.isMaster) {
return this.masterGet(key);
} else {
const localItem = this.cache.get(key);
if (localItem) {
if (localItem.expiresAt && Date.now() > localItem.expiresAt) {
this.localDelete(key);
} else {
return localItem.value;
}
}
const requestId = `${Date.now()}_${Math.random()}`;
return new Promise((resolve) => {
const timeout = setTimeout(() => {
this.removeListener(`get_response_${requestId}`, responseHandler);
resolve(undefined);
}, 5000);
const responseHandler = (value) => {
clearTimeout(timeout);
resolve(value);
};
this.once(`get_response_${requestId}`, responseHandler);
process.send({
type: 'CACHE_GET',
key,
requestId
});
});
}
}
async delete(key) {
if (cluster.isMaster) {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
} else {
process.send({
type: 'CACHE_DELETE',
key
});
}
}
async clear() {
if (cluster.isMaster) {
this.masterClear();
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_CLEAR'
});
} else {
process.send({
type: 'CACHE_CLEAR'
});
}
}
has(key) {
const item = this.cache.get(key);
if (!item) return false;
if (item.expiresAt && Date.now() > item.expiresAt) {
this.localDelete(key);
return false;
}
return true;
}
size() {
return this.cache.size;
}
keys() {
return Array.from(this.cache.keys());
}
values() {
return Array.from(this.cache.values()).map(item => item.value);
}
entries() {
return Array.from(this.cache.entries()).map(([key, item]) => [key, item.value]);
}
startCleanup() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.cleanupInterval = setInterval(() => {
const now = Date.now();
const expiredKeys = [];
for (const [key, item] of this.cache.entries()) {
if (item.expiresAt && now > item.expiresAt) {
expiredKeys.push(key);
}
}
expiredKeys.forEach(key => this.localDelete(key));
// Master cleanup
if (cluster.isMaster && this.masterCache) {
const expiredMasterKeys = [];
for (const [key, item] of this.masterCache.entries()) {
if (item.expiresAt && now > item.expiresAt) {
expiredMasterKeys.push(key);
}
}
expiredMasterKeys.forEach(key => {
this.masterDelete(key);
this.emit('masterCacheUpdate', {
type: 'CACHE_SYNC_DELETE',
key
});
});
}
}, this.options.checkPeriod);
}
destroy() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
this.timers.forEach(timer => clearTimeout(timer));
this.timers.clear();
if (this.masterTimers) {
this.masterTimers.forEach(timer => clearTimeout(timer));
this.masterTimers.clear();
}
this.removeAllListeners();
}
}
module.exports = ClusterSharedCache;