@telepilotco/n8n-nodes-kv-storage
Version:
Key-Value Storage Node for n8n supporting different scopes
335 lines • 13.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.KvStorageService = exports.EventType = exports.Scope = void 0;
require("reflect-metadata");
const typedi_1 = require("typedi");
const debug = require('debug')('kv-storage');
var Scope;
(function (Scope) {
Scope["ALL"] = "ALL";
Scope["EXECUTION"] = "EXECUTION";
Scope["WORKFLOW"] = "WORKFLOW";
Scope["INSTANCE"] = "INSTANCE";
})(Scope = exports.Scope || (exports.Scope = {}));
var EventType;
(function (EventType) {
EventType["ANY"] = "ANY";
EventType["ADDED"] = "added";
EventType["UPDATED"] = "updated";
EventType["DELETED"] = "deleted";
})(EventType = exports.EventType || (exports.EventType = {}));
let KvStorageService = class KvStorageService {
constructor() {
this.map = {};
this.mapExpiration = {};
this.workflowListenersMap = {};
this.instanceListeners = [];
this.executionListeners = [];
this.allListeners = [];
debug('constructor');
setInterval(() => {
debug('setInterval');
this.deleteExpiredEntries();
}, 1 * 1000);
}
deleteExpiredEntries() {
const expirationKeys = Object.keys(this.mapExpiration);
debug('deleteExpiredEntries.length=' + expirationKeys.length);
const expiredKeys = expirationKeys.filter((k) => {
return this.mapExpiration[k] <= Date.now();
});
expiredKeys.map((k) => {
debug('deleteing expired key=' + k);
this.deleteKeyWithScopedKey(k);
});
}
listAllKeyValuesInAllScopes() {
debug('listAllKeyValuesInAllScopes: ');
const mapKeys = Object.keys(this.map);
const regExp = /scope:(\w+)-(.+):(.*)/;
const matchedEntries = {};
mapKeys
.filter((scopedKey) => scopedKey.match(regExp))
.map((scopedKey) => {
const m = scopedKey.match(regExp);
const scope = m[1];
const specifier = m[2];
const key = m[3];
const entryKey = `${scope}-${specifier}`;
if (Object.keys(matchedEntries).includes(entryKey)) {
matchedEntries[entryKey].entries[key] = this.map[scopedKey];
}
else {
const e = {
scope,
specifier,
entries: { [key]: this.map[scopedKey] },
};
if (Object.keys(this.mapExpiration).includes(scopedKey)) {
e.expiresAt = this.mapExpiration[scopedKey];
}
matchedEntries[entryKey] = e;
}
});
return matchedEntries;
}
listAllKeysInScope(scope, specifier = '') {
debug('getAllKeysInScope: scope=' + scope + ';specifier=' + specifier);
const mapKeys = Object.keys(this.map);
const regExp = new RegExp(`scope\\:${scope}-${specifier}\\:.*`, 'g');
const matchedKeys = mapKeys
.filter((scopedKey) => scopedKey.match(regExp))
.map((scopedKey) => this.getKey(scopedKey));
return { keys: matchedKeys, scope, specifier };
}
listAllKeyValuesInScope(scope, specifier = '') {
debug('getAllKeyValuesInScope: scope=' + scope + ';specifier=' + specifier);
const regExp = new RegExp(`scope\\:${scope}-${specifier}\\:.*`, 'g');
const mapKeys = Object.keys(this.map);
const matchedKeys = mapKeys.filter((k) => k.match(regExp));
const matchedEntries = {};
matchedKeys.map((sK) => {
const k = this.getKey(sK);
matchedEntries[k] = this.map[sK];
});
return { entries: matchedEntries, scope, specifier };
}
deleteKeyWithScopedKey(scopedKey) {
const scopeKeyOf = this.getScope(scopedKey);
const scope = Scope[scopeKeyOf];
const getSpecifier = this.getSpecifier(scopedKey);
const key = this.getKey(scopedKey);
return this.deleteKey(key, scope, getSpecifier);
}
deleteKey(key, scope, specifier = '') {
debug('deleteKey: key=' + key + ';scope=' + scope + ';specifier=' + specifier);
const scopedKey = this.composeScopeKey(key, scope, specifier);
const mapKeys = Object.keys(this.map);
let res = false;
let val_ = [];
const eventType = EventType.DELETED;
const timestamp = Date.now();
if (mapKeys.includes(scopedKey)) {
res = true;
val_ = this.map[scopedKey];
delete this.map[scopedKey];
delete this.mapExpiration[scopedKey];
}
const event = {
eventType,
scope,
specifier,
key,
val: val_,
timestamp,
};
this.sendEvent(event, scope, specifier);
return { res };
}
getValue(key, scope, specifier = '') {
debug('getValue: key=' + key + ';scope=' + scope + ';specifier=' + specifier);
const scopedKey = this.composeScopeKey(key, scope, specifier);
return { val: this.map[scopedKey] };
}
incrementValue(key, scope, specifier = '', ttl = -1) {
debug('incrementValue: key=' + key + ';scope=' + scope + ';specifier=' + specifier);
const scopedKey = this.composeScopeKey(key, scope, specifier);
let expiresAt = -1;
if (ttl > -1) {
expiresAt = Date.now() + ttl * 1000;
this.mapExpiration[scopedKey] = expiresAt;
debug('expiresAt=' + expiresAt);
}
const timestamp = Date.now();
let eventType = EventType.ADDED;
if (Object.keys(this.map).includes(scopedKey)) {
eventType = EventType.UPDATED;
}
let oldVal = Number(this.map[scopedKey]);
if (!oldVal || oldVal === undefined) {
oldVal = 0;
}
this.map[scopedKey] = [oldVal + 1];
const event = {
eventType,
scope,
specifier,
key,
val: this.map[scopedKey],
timestamp,
expiresAt,
};
if (Object.keys(this.map).includes(scopedKey)) {
event.oldVal = oldVal;
event.eventType = eventType;
}
this.sendEvent(event, scope, specifier);
return { val: this.map[scopedKey] };
}
setValue(key, val, scope, specifier = '', ttl = -1) {
debug('setValue: key=' + key + ';val=' + val + ';scope=' + scope + ';specifier=' + specifier);
const scopedKey = this.composeScopeKey(key, scope, specifier);
let expiresAt = -1;
if (ttl > -1) {
expiresAt = Date.now() + ttl * 1000;
this.mapExpiration[scopedKey] = expiresAt;
debug('expiresAt=' + expiresAt);
}
const timestamp = Date.now();
let eventType = EventType.ADDED;
if (Object.keys(this.map).includes(scopedKey)) {
eventType = EventType.UPDATED;
}
const oldVal = this.map[scopedKey];
this.map[scopedKey] = [val];
const event = {
eventType,
scope,
specifier,
key,
val,
timestamp,
expiresAt,
};
if (Object.keys(this.map).includes(scopedKey)) {
event.oldVal = oldVal;
event.eventType = eventType;
}
this.sendEvent(event, scope, specifier);
return { val: this.map[scopedKey] };
}
sendEvent(event, scope, specifier) {
if (this.allListeners.length > 0) {
this.allListeners.map((callback) => callback(event));
}
if (scope === Scope.INSTANCE && this.instanceListeners.length > 0) {
this.instanceListeners.map((callback) => callback(event));
}
if (scope === Scope.EXECUTION && this.executionListeners.length > 0) {
this.executionListeners.map((callback) => callback(event));
}
if (scope === Scope.WORKFLOW) {
Object.keys(this.workflowListenersMap).map((k) => {
if (specifier === k) {
this.workflowListenersMap[Number(k)].map((callback) => callback(event));
}
});
}
}
composeScopeKey(key, scope, specifier = '') {
debug('composeScopeKey: key=' + key + ';scope=' + scope + ';specifier=' + specifier);
if (scope === Scope.EXECUTION) {
return `scope:${scope}-${specifier}:${key}`;
}
else if (scope === Scope.WORKFLOW) {
return `scope:${scope}-${specifier}:${key}`;
}
else if (scope === Scope.INSTANCE) {
return `scope:${scope}-${specifier}:${key}`;
}
const scopedKey = `scope:${scope}:${key}`;
debug('scopedKey=' + scopedKey);
return scopedKey;
}
getKey(scopedKey) {
const match = scopedKey.match(/scope:\w+-.*:(.*)/);
return match !== null ? match[1] : 'EMPTY';
}
getScope(scopedKey) {
const match = scopedKey.match(/scope:(\w+)-.*:.*/);
return match !== null ? match[1] : 'EMPTY';
}
getSpecifier(scopedKey) {
const match = scopedKey.match(/scope:\w+-(.*):.*/);
return match !== null ? match[1] : 'EMPTY';
}
addListener(scope, specifier, callback) {
debug('addListener: scope=' + scope + ';specifier=' + specifier);
if (scope === Scope.WORKFLOW) {
const workflowListenersMapKeys = Object.keys(this.workflowListenersMap);
debug('workflowListenersMapKeys: ' + workflowListenersMapKeys);
const keys = specifier
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0);
keys.map((key) => {
if (!workflowListenersMapKeys.includes(key)) {
debug('initialized with callback');
this.workflowListenersMap[Number(key)] = [callback];
}
else {
debug('pushed callback');
this.workflowListenersMap[Number(key)].push(callback);
}
});
}
else if (scope === Scope.EXECUTION) {
debug('pushed callback');
this.executionListeners.push(callback);
debug('this.executionListeners.length=' + this.executionListeners.length);
}
else if (scope === Scope.INSTANCE) {
debug('pushed callback');
this.instanceListeners.push(callback);
debug('this.instanceListeners.length=' + this.instanceListeners.length);
}
else if (scope === Scope.ALL) {
debug('pushed callback');
this.allListeners.push(callback);
debug('this.allListeners.length=' + this.allListeners.length);
}
}
removeListener(scope, specifier, callback) {
debug('removeListener: scope=' + scope + ';specifier=' + specifier);
if (scope === Scope.WORKFLOW) {
const workflowListenersMapKeys = Object.keys(this.workflowListenersMap);
const keys = specifier
.split(',')
.map((s) => s.trim())
.filter((s) => s.length > 0);
keys.map((key) => {
if (workflowListenersMapKeys.includes(key)) {
debug('this.workflowListenersMap[' +
key +
'].length=' +
this.workflowListenersMap[Number(key)].length);
this.workflowListenersMap[Number(key)] = this.workflowListenersMap[Number(key)].filter((cb) => cb !== callback);
debug('this.workflowListenersMap[' +
key +
'].length=' +
this.workflowListenersMap[Number(key)].length);
}
});
}
else if (scope === Scope.EXECUTION) {
debug('this.executionListeners.length=' + this.executionListeners.length);
this.executionListeners = this.executionListeners.filter((cb) => cb !== callback);
debug('this.executionListeners.length=' + this.executionListeners.length);
}
else if (scope === Scope.INSTANCE) {
debug('this.instanceListeners.length=' + this.instanceListeners.length);
this.instanceListeners = this.instanceListeners.filter((cb) => cb !== callback);
debug('this.instanceListeners.length=' + this.instanceListeners.length);
}
else if (scope === Scope.ALL) {
debug('this.allListeners.length=' + this.allListeners.length);
this.allListeners = this.allListeners.filter((cb) => cb !== callback);
debug('this.allListeners.length=' + this.allListeners.length);
}
}
};
KvStorageService = __decorate([
(0, typedi_1.Service)(),
__metadata("design:paramtypes", [])
], KvStorageService);
exports.KvStorageService = KvStorageService;
//# sourceMappingURL=KvStorageService.js.map