@autofleet/matmon 1.0.5 → 2.0.0-beta-40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +2 -1
- package/lib/index.js +3 -1
- package/lib/logger.d.ts +2 -0
- package/lib/logger.js +5 -0
- package/lib/orm-cache/adapter.d.ts +14 -0
- package/lib/orm-cache/adapter.js +2 -0
- package/lib/orm-cache/errors.d.ts +2 -0
- package/lib/orm-cache/errors.js +6 -0
- package/lib/orm-cache/index.d.ts +13 -0
- package/lib/orm-cache/index.js +39 -0
- package/lib/orm-cache/sequelize-adapter.d.ts +12 -0
- package/lib/orm-cache/sequelize-adapter.js +113 -0
- package/lib/redis/index.d.ts +2 -0
- package/lib/redis/index.js +15 -1
- package/package.json +4 -2
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -3,9 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.RedisCache = exports.getWithCacheSupport = exports.getNewLRU = void 0;
|
|
6
|
+
exports.ORMCache = exports.RedisCache = exports.getWithCacheSupport = exports.getNewLRU = void 0;
|
|
7
7
|
const cache_1 = require("./cache");
|
|
8
8
|
Object.defineProperty(exports, "getNewLRU", { enumerable: true, get: function () { return cache_1.getNewLRU; } });
|
|
9
9
|
Object.defineProperty(exports, "getWithCacheSupport", { enumerable: true, get: function () { return cache_1.getWithCacheSupport; } });
|
|
10
10
|
const redis_1 = __importDefault(require("./redis"));
|
|
11
11
|
exports.RedisCache = redis_1.default;
|
|
12
|
+
const orm_cache_1 = require("./orm-cache");
|
|
13
|
+
Object.defineProperty(exports, "ORMCache", { enumerable: true, get: function () { return orm_cache_1.ORMCache; } });
|
package/lib/logger.d.ts
ADDED
package/lib/logger.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import RedisCache from '../redis';
|
|
2
|
+
export interface ModelOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
associations: {
|
|
5
|
+
name: string;
|
|
6
|
+
alias: string;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
export interface Adapter {
|
|
10
|
+
ormInstance: any;
|
|
11
|
+
getModel: (modelName: string) => any;
|
|
12
|
+
injectGetWithCacheFunction: (cache: RedisCache, modelOptions: ModelOptions) => void;
|
|
13
|
+
addInvalidationHooks: (cache: RedisCache, modelOptions: ModelOptions) => void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ModelOptions } from './adapter';
|
|
2
|
+
declare enum ORMTypes {
|
|
3
|
+
SEQUELIZE = "sequelize"
|
|
4
|
+
}
|
|
5
|
+
interface ORMCacheOptions {
|
|
6
|
+
type: ORMTypes;
|
|
7
|
+
models: ModelOptions[];
|
|
8
|
+
ormInstance: any;
|
|
9
|
+
debug: boolean;
|
|
10
|
+
ttl: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const ORMCache: (options: ORMCacheOptions) => void;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ORMCache = void 0;
|
|
7
|
+
const sequelize_adapter_1 = __importDefault(require("./sequelize-adapter"));
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const redis_1 = __importDefault(require("../redis"));
|
|
10
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
11
|
+
var ORMTypes;
|
|
12
|
+
(function (ORMTypes) {
|
|
13
|
+
ORMTypes["SEQUELIZE"] = "sequelize";
|
|
14
|
+
})(ORMTypes || (ORMTypes = {}));
|
|
15
|
+
const ORMInstanceFactory = (options) => {
|
|
16
|
+
switch (options.type) {
|
|
17
|
+
case ORMTypes.SEQUELIZE: {
|
|
18
|
+
return new sequelize_adapter_1.default(options.ormInstance, options.debug);
|
|
19
|
+
}
|
|
20
|
+
default: {
|
|
21
|
+
throw new errors_1.UnsupportedOrmTypeError(`ORM type ${options.type} is unsupported at the moment`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
exports.ORMCache = (options) => {
|
|
26
|
+
const { type, models, ormInstance, } = options;
|
|
27
|
+
logger_1.default.info('Starting ORM Cache', { options });
|
|
28
|
+
const adapter = ORMInstanceFactory(options);
|
|
29
|
+
const cache = new redis_1.default({
|
|
30
|
+
host: process.env.REDIS_HOST,
|
|
31
|
+
port: process.env.REDIS_PORT,
|
|
32
|
+
ttl: options.ttl || 90,
|
|
33
|
+
});
|
|
34
|
+
// eslint-disable-next-line array-callback-return
|
|
35
|
+
models.map((modelOptions) => {
|
|
36
|
+
adapter.addInvalidationHooks(cache, modelOptions);
|
|
37
|
+
adapter.injectGetWithCacheFunction(cache, modelOptions);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Sequelize } from 'sequelize';
|
|
2
|
+
import { Adapter, ModelOptions } from './adapter';
|
|
3
|
+
import RedisCache from '../redis';
|
|
4
|
+
export default class SequelizeAdapter implements Adapter {
|
|
5
|
+
ormInstance: Sequelize;
|
|
6
|
+
debugMode: boolean;
|
|
7
|
+
constructor(sequelize: Sequelize, debug: any);
|
|
8
|
+
getModel(modelName: string): any;
|
|
9
|
+
debug(message: any, payload: any): void;
|
|
10
|
+
injectGetWithCacheFunction(cache: RedisCache, modelOptions: ModelOptions): void;
|
|
11
|
+
addInvalidationHooks(cache: RedisCache, modelOptions: ModelOptions): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
16
|
+
const util_1 = require("util");
|
|
17
|
+
const { AF_SERVICE_NAME } = process.env;
|
|
18
|
+
const INVALIDATION_HOOKS = ['afterSave', 'afterUpdate', 'afterDestroy'];
|
|
19
|
+
const BULK_HOOKS = ['beforeBulkUpdate', 'beforeBulkDestroy'];
|
|
20
|
+
const generateInstanceKey = (modelOptions, id) => `${AF_SERVICE_NAME}:${modelOptions.name}_${id}_INCLUDING_${modelOptions.associations.map(a => a.name).join('_')}`;
|
|
21
|
+
const generateDependencyKey = (modelName, associationName, associationId) => `${AF_SERVICE_NAME}:${modelName}_${associationName}_${associationId}_DEPENDENCIES`;
|
|
22
|
+
const handleTransactionHook = (instance, options, func) => {
|
|
23
|
+
const { transaction } = options;
|
|
24
|
+
if (transaction) {
|
|
25
|
+
transaction.afterCommit(() => func(instance));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
func(instance);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
class SequelizeAdapter {
|
|
32
|
+
constructor(sequelize, debug) {
|
|
33
|
+
this.ormInstance = sequelize;
|
|
34
|
+
this.debugMode = debug;
|
|
35
|
+
}
|
|
36
|
+
getModel(modelName) {
|
|
37
|
+
return this.ormInstance.models[modelName];
|
|
38
|
+
}
|
|
39
|
+
debug(message, payload) {
|
|
40
|
+
if (this.debugMode) {
|
|
41
|
+
logger_1.default.info(`[ORM_CACHE Debug] ${message}`, payload);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
injectGetWithCacheFunction(cache, modelOptions) {
|
|
45
|
+
const addDependencies = (modelName, instance) => __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const dependencyKeys = modelOptions.associations.map(association => generateDependencyKey(modelName, association.name, instance[association.alias].id));
|
|
47
|
+
const instanceKey = generateInstanceKey(modelOptions, instance.id);
|
|
48
|
+
this.debug('Adding dependencies', { instanceKey, dependencyKeys });
|
|
49
|
+
const addDependenciesMulti = cache.getClient().multi();
|
|
50
|
+
const addDependenciesMultiAsync = util_1.promisify(addDependenciesMulti.exec).bind(addDependenciesMulti);
|
|
51
|
+
dependencyKeys.reduce((multi, key) => multi.sadd(key, instanceKey), addDependenciesMulti);
|
|
52
|
+
return addDependenciesMultiAsync();
|
|
53
|
+
});
|
|
54
|
+
const model = this.getModel(modelOptions.name);
|
|
55
|
+
model.findByPkCached = (id, scopes, options) => __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
const cacheKey = generateInstanceKey(modelOptions, id);
|
|
57
|
+
let value = JSON.parse(yield cache.getClient().getAsync(cacheKey));
|
|
58
|
+
if (!value) {
|
|
59
|
+
this.debug('Value not found in cache, looking in db', { id, cacheKey });
|
|
60
|
+
value = yield model.scope(scopes).findByPk(id, options);
|
|
61
|
+
this.debug('Value from DB', { value: value || 'not found', cacheKey });
|
|
62
|
+
const [setRes] = yield Promise.all([
|
|
63
|
+
cache.getClient().setAsync(cacheKey, JSON.stringify(value)),
|
|
64
|
+
value && addDependencies(modelOptions.name, value),
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
this.debug('Found cached value', { value, id, cacheKey });
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
addInvalidationHooks(cache, modelOptions) {
|
|
74
|
+
const invalidateModelInstance = (modelName, instance) => __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const dependencyKeys = modelOptions.associations
|
|
76
|
+
.filter(association => instance[association.alias])
|
|
77
|
+
.map(association => generateDependencyKey(modelName, association.name, instance[association.alias].id));
|
|
78
|
+
const instanceKey = generateInstanceKey(modelOptions, instance.id);
|
|
79
|
+
this.debug('Removing dependencies', { instance, instanceKey, dependencyKeys });
|
|
80
|
+
const removeMulti = cache.getClient().multi();
|
|
81
|
+
const removeMultiAsync = util_1.promisify(removeMulti.exec).bind(removeMulti);
|
|
82
|
+
dependencyKeys.map(key => removeMulti.srem(key, instanceKey));
|
|
83
|
+
removeMulti.del(instanceKey);
|
|
84
|
+
return removeMultiAsync();
|
|
85
|
+
});
|
|
86
|
+
const invalidateModelInstanceByAssociation = (modelName, association, associationId) => __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const dependentInstancesKeys = yield cache.getClient().smembersAsync(generateDependencyKey(modelName, association, associationId));
|
|
88
|
+
this.debug('Invalidating dependent instances', { dependentInstancesKeys });
|
|
89
|
+
const removeMulti = cache.getClient().multi();
|
|
90
|
+
const removeMultiAsync = util_1.promisify(removeMulti.exec).bind(removeMulti);
|
|
91
|
+
const dependenciesToRemove = yield Promise.all(dependentInstancesKeys.map((instanceKey) => __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const instance = JSON.parse(yield cache.getClient().getAsync(instanceKey));
|
|
93
|
+
const dependencyKeys = modelOptions.associations
|
|
94
|
+
.filter(association => instance[association.alias])
|
|
95
|
+
.map(association => generateDependencyKey(modelName, association.name, instance[association.alias].id));
|
|
96
|
+
dependencyKeys.reduce((multi, key) => multi.srem(key, instanceKey), removeMulti);
|
|
97
|
+
removeMulti.del(instanceKey);
|
|
98
|
+
return dependencyKeys;
|
|
99
|
+
})));
|
|
100
|
+
this.debug('Removing dependencies', { dependentInstancesKeys, dependenciesToRemove });
|
|
101
|
+
return removeMultiAsync();
|
|
102
|
+
});
|
|
103
|
+
const model = this.getModel(modelOptions.name);
|
|
104
|
+
INVALIDATION_HOOKS.map(hook => model.addHook(hook, (instance, options) => handleTransactionHook(instance, options, instance => invalidateModelInstance(modelOptions.name, instance))));
|
|
105
|
+
BULK_HOOKS.map(hook => model.addHook(hook, options => options.individualHook = true));
|
|
106
|
+
modelOptions.associations.map(association => {
|
|
107
|
+
const associationModel = this.getModel(association.name);
|
|
108
|
+
INVALIDATION_HOOKS.map(hook => associationModel.addHook(hook, (instance, options) => handleTransactionHook(instance, options, ({ id }) => invalidateModelInstanceByAssociation(modelOptions.name, association.name, id))));
|
|
109
|
+
BULK_HOOKS.map(hook => associationModel.addHook(hook, options => options.individualHook = true));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.default = SequelizeAdapter;
|
package/lib/redis/index.d.ts
CHANGED
package/lib/redis/index.js
CHANGED
|
@@ -95,10 +95,24 @@ class RedisCache {
|
|
|
95
95
|
yield this.client.delAsync(keyWithPrefix);
|
|
96
96
|
}
|
|
97
97
|
catch (err) {
|
|
98
|
-
throw new errors_1.RedisCacheError(`Failed to
|
|
98
|
+
throw new errors_1.RedisCacheError(`Failed to delete key ${keyWithPrefix}`, err);
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
removeMultiple(keys) {
|
|
103
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
+
const keysWithPrefix = keys.map(key => KEY_PREFIX + key);
|
|
105
|
+
try {
|
|
106
|
+
yield this.client.delAsync(keysWithPrefix);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new errors_1.RedisCacheError(`Failed to delete multiple keys ${keysWithPrefix.join('|')}`, err);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
getClient() {
|
|
114
|
+
return this.client;
|
|
115
|
+
}
|
|
102
116
|
lock(key) {
|
|
103
117
|
return Promise.race([
|
|
104
118
|
this.locker(key, this.lockDuration),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/matmon",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta-40",
|
|
4
4
|
"description": "manage cache",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
"dotenv": "^8.2.0",
|
|
34
34
|
"lru-cache": "^6.0.0",
|
|
35
35
|
"redis": "^3.0.2",
|
|
36
|
-
"redis-lock": "^0.1.4"
|
|
36
|
+
"redis-lock": "^0.1.4",
|
|
37
|
+
"sequelize": "^6.6.2",
|
|
38
|
+
"sequelize-typescript": "^2.1.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"typescript": "^3.9.5"
|