@autofleet/matmon 2.0.1 → 2.0.4-beta-1

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 CHANGED
@@ -1,4 +1,4 @@
1
1
  import { getNewLRU, getWithCacheSupport } from './cache';
2
2
  import RedisCache from './redis';
3
- import { ORMCache } from './orm-cache';
4
- export { getNewLRU, getWithCacheSupport, RedisCache, ORMCache, };
3
+ import { ORMCache, ORMTypes } from './orm-cache';
4
+ export { getNewLRU, getWithCacheSupport, RedisCache, ORMCache, ORMTypes };
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@ 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.ORMCache = exports.RedisCache = exports.getWithCacheSupport = exports.getNewLRU = void 0;
6
+ exports.ORMTypes = 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; } });
@@ -11,3 +11,4 @@ const redis_1 = __importDefault(require("./redis"));
11
11
  exports.RedisCache = redis_1.default;
12
12
  const orm_cache_1 = require("./orm-cache");
13
13
  Object.defineProperty(exports, "ORMCache", { enumerable: true, get: function () { return orm_cache_1.ORMCache; } });
14
+ Object.defineProperty(exports, "ORMTypes", { enumerable: true, get: function () { return orm_cache_1.ORMTypes; } });
@@ -1,13 +1,13 @@
1
1
  import RedisCache from '../redis';
2
2
  export interface ModelOptions {
3
3
  name: string;
4
- associations: AssociationOptions[];
4
+ associations?: AssociationOptions[];
5
5
  }
6
6
  export interface AssociationOptions {
7
7
  name: string;
8
8
  alias: string;
9
9
  accessKey?: string;
10
- associations?: AssociationOptions[];
10
+ innerAssociation?: AssociationOptions;
11
11
  }
12
12
  export interface Adapter {
13
13
  ormInstance: any;
@@ -1,5 +1,5 @@
1
1
  import { ModelOptions } from './adapter';
2
- declare enum ORMTypes {
2
+ export declare enum ORMTypes {
3
3
  SEQUELIZE = "sequelize"
4
4
  }
5
5
  interface ORMCacheOptions {
@@ -7,7 +7,6 @@ interface ORMCacheOptions {
7
7
  models: ModelOptions[];
8
8
  ormInstance: any;
9
9
  debug: boolean;
10
- ttl: number;
11
10
  }
12
11
  export declare const ORMCache: (options: ORMCacheOptions) => void;
13
12
  export {};
@@ -3,7 +3,7 @@ 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.ORMCache = void 0;
6
+ exports.ORMCache = exports.ORMTypes = void 0;
7
7
  const sequelize_adapter_1 = __importDefault(require("./sequelize-adapter"));
8
8
  const errors_1 = require("./errors");
9
9
  const redis_1 = __importDefault(require("../redis"));
@@ -11,7 +11,7 @@ const logger_1 = __importDefault(require("../logger"));
11
11
  var ORMTypes;
12
12
  (function (ORMTypes) {
13
13
  ORMTypes["SEQUELIZE"] = "sequelize";
14
- })(ORMTypes || (ORMTypes = {}));
14
+ })(ORMTypes = exports.ORMTypes || (exports.ORMTypes = {}));
15
15
  const ORMInstanceFactory = (options) => {
16
16
  switch (options.type) {
17
17
  case ORMTypes.SEQUELIZE: {
@@ -23,13 +23,12 @@ const ORMInstanceFactory = (options) => {
23
23
  }
24
24
  };
25
25
  exports.ORMCache = (options) => {
26
- const { type, models, ormInstance, } = options;
26
+ const { models } = options;
27
27
  logger_1.default.info('Starting ORM Cache', { options });
28
28
  const adapter = ORMInstanceFactory(options);
29
29
  const cache = new redis_1.default({
30
30
  host: process.env.REDIS_HOST,
31
31
  port: process.env.REDIS_PORT,
32
- ttl: options.ttl || 90,
33
32
  });
34
33
  // eslint-disable-next-line array-callback-return
35
34
  models.map((modelOptions) => {
@@ -1,11 +1,12 @@
1
1
  import { Sequelize } from 'sequelize';
2
- import { Adapter, ModelOptions } from './adapter';
2
+ import { Adapter, AssociationOptions, ModelOptions } from './adapter';
3
3
  import RedisCache from '../redis';
4
4
  export default class SequelizeAdapter implements Adapter {
5
5
  ormInstance: Sequelize;
6
6
  debugMode: boolean;
7
7
  constructor(sequelize: Sequelize, debug: any);
8
8
  getModel(modelName: string): any;
9
+ getModelDependencies(modelName: string): AssociationOptions[];
9
10
  debug(message: any, payload: any): void;
10
11
  injectGetWithCacheFunction(cache: RedisCache, modelOptions: ModelOptions): void;
11
12
  addInvalidationHooks(cache: RedisCache, modelOptions: ModelOptions): void;
@@ -12,33 +12,37 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ const newrelic_1 = __importDefault(require("newrelic"));
15
16
  const logger_1 = __importDefault(require("../logger"));
16
17
  const util_1 = require("util");
17
18
  const { AF_SERVICE_NAME } = process.env;
19
+ const ORM_CACHE_PREFIX = 'ormCache';
18
20
  const INVALIDATION_HOOKS = ['afterSave', 'afterUpdate', 'afterDestroy'];
19
21
  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 getAssociation = (instance, associationOptions, associationId) => {
23
- const accessKey = associationOptions.accessKey || 'id';
24
- if (Array.isArray(instance[associationOptions.alias])) {
25
- return instance[associationOptions.alias].find(association => association[accessKey] === associationId);
26
- }
27
- return instance[associationOptions.alias];
28
- };
29
- const getInstanceDependencyKeys = (modelOptions, instance) => {
22
+ const generateInstanceKey = (name, id) => `${AF_SERVICE_NAME}:${ORM_CACHE_PREFIX}:${name}_${id}`;
23
+ const generateDependencyKey = (modelName, associationName, associationId) => `${AF_SERVICE_NAME}:${ORM_CACHE_PREFIX}:${modelName}_${associationName}_${associationId}_DEPENDENCIES`;
24
+ const getInstanceDependencyKeys = (modelOptions, instance) => newrelic_1.default.startSegment('getInstanceDependencyKeys', true, () => {
30
25
  const keys = modelOptions.associations
31
- .filter((association) => instance[association.alias])
26
+ .filter((associationOptions) => instance[associationOptions.alias])
32
27
  .map((associationOptions) => {
33
- const accessKey = associationOptions.accessKey || 'id';
28
+ const accessKey = associationOptions.accessKey;
29
+ const depKeys = [];
34
30
  if (Array.isArray(instance[associationOptions.alias])) {
35
- return instance[associationOptions.alias]
36
- .map(associationInstance => generateDependencyKey(modelOptions.name, associationOptions.name, associationInstance[accessKey]));
31
+ instance[associationOptions.alias].map(associationInstance => {
32
+ depKeys.push(generateDependencyKey(modelOptions.name, associationOptions.name, associationInstance[accessKey]));
33
+ if (associationOptions.innerAssociation &&
34
+ associationInstance[associationOptions.innerAssociation.alias]) {
35
+ depKeys.push(generateDependencyKey(modelOptions.name, associationOptions.innerAssociation.name, associationInstance[associationOptions.innerAssociation.alias][associationOptions.innerAssociation.accessKey]));
36
+ }
37
+ });
38
+ return depKeys;
37
39
  }
38
- return [generateDependencyKey(modelOptions.name, associationOptions.name, instance[associationOptions.alias][accessKey])];
40
+ return [
41
+ generateDependencyKey(modelOptions.name, associationOptions.name, instance[associationOptions.alias][accessKey]),
42
+ ];
39
43
  });
40
44
  return keys.reduce((flattenArray, array) => flattenArray.concat(array), []);
41
- };
45
+ });
42
46
  const handleTransactionHook = (instance, options, func) => {
43
47
  const { transaction } = options;
44
48
  if (transaction) {
@@ -56,15 +60,39 @@ class SequelizeAdapter {
56
60
  getModel(modelName) {
57
61
  return this.ormInstance.models[modelName];
58
62
  }
63
+ getModelDependencies(modelName) {
64
+ return newrelic_1.default.startSegment('getModelDependencies', true, () => {
65
+ const { associations } = this.ormInstance.models[modelName];
66
+ return [
67
+ ...Object.keys(associations).map(association => {
68
+ const sequelizeAssociation = associations[association];
69
+ const associationOptions = {
70
+ alias: sequelizeAssociation.as,
71
+ name: sequelizeAssociation.target.name,
72
+ accessKey: sequelizeAssociation.target.primaryKeyAttribute,
73
+ };
74
+ if (sequelizeAssociation.through) {
75
+ const relationModel = sequelizeAssociation.through.model;
76
+ associationOptions.innerAssociation = {
77
+ alias: relationModel.name,
78
+ name: relationModel.name,
79
+ accessKey: relationModel.primaryKeyAttribute,
80
+ };
81
+ }
82
+ return associationOptions;
83
+ }),
84
+ ];
85
+ });
86
+ }
59
87
  debug(message, payload) {
60
88
  if (this.debugMode) {
61
89
  logger_1.default.info(`[ORM_CACHE Debug] ${message}`, payload);
62
90
  }
63
91
  }
64
92
  injectGetWithCacheFunction(cache, modelOptions) {
65
- const addDependencies = (instance) => __awaiter(this, void 0, void 0, function* () {
93
+ const addDependencies = (instance) => newrelic_1.default.startSegment('addDependencies', true, () => {
66
94
  const dependencyKeys = getInstanceDependencyKeys(modelOptions, instance);
67
- const instanceKey = generateInstanceKey(modelOptions, instance.id);
95
+ const instanceKey = generateInstanceKey(modelOptions.name, instance.id);
68
96
  this.debug('Adding dependencies', { instanceKey, dependencyKeys });
69
97
  const addDependenciesMulti = cache.getClient().multi();
70
98
  const addDependenciesMultiAsync = util_1.promisify(addDependenciesMulti.exec).bind(addDependenciesMulti);
@@ -73,37 +101,38 @@ class SequelizeAdapter {
73
101
  });
74
102
  const model = this.getModel(modelOptions.name);
75
103
  model.findByPkCached = (id, scopes, options) => __awaiter(this, void 0, void 0, function* () {
76
- const cacheKey = generateInstanceKey(modelOptions, id);
104
+ const cacheKey = generateInstanceKey(modelOptions.name, id);
77
105
  let value = JSON.parse(yield cache.getClient().getAsync(cacheKey));
78
106
  if (!value) {
79
107
  this.debug('Value not found in cache, looking in db', { id, cacheKey });
80
108
  value = yield model.scope(scopes).findByPk(id, options);
81
109
  this.debug('Value from DB', { value: value || 'not found', cacheKey });
82
- const [setRes] = yield Promise.all([
110
+ yield Promise.all([
83
111
  cache.getClient().setAsync(cacheKey, JSON.stringify(value)),
84
112
  value && addDependencies(value),
85
113
  ]);
86
114
  }
87
115
  else {
116
+ value = this.getModel(modelOptions.name).build(value, { isNewRecord: false, include: options.include });
88
117
  this.debug('Found cached value', { value, id, cacheKey });
89
118
  }
90
119
  return value;
91
120
  });
92
121
  }
93
122
  addInvalidationHooks(cache, modelOptions) {
94
- const invalidateModelInstance = (instance) => __awaiter(this, void 0, void 0, function* () {
123
+ const invalidateModelInstance = (hook, instance) => newrelic_1.default.startSegment('invalidateModelInstance', true, () => {
95
124
  const dependencyKeys = getInstanceDependencyKeys(modelOptions, instance);
96
- const instanceKey = generateInstanceKey(modelOptions, instance.id);
97
- this.debug('Removing dependencies', { instance, instanceKey, dependencyKeys });
125
+ const instanceKey = generateInstanceKey(modelOptions.name, instance.id);
126
+ this.debug(`Removing dependencies (triggered by ${hook})`, { instance, instanceKey, dependencyKeys });
98
127
  const removeMulti = cache.getClient().multi();
99
128
  const removeMultiAsync = util_1.promisify(removeMulti.exec).bind(removeMulti);
100
129
  dependencyKeys.map(key => removeMulti.srem(key, instanceKey));
101
130
  removeMulti.del(instanceKey);
102
131
  return removeMultiAsync();
103
132
  });
104
- const invalidateModelInstanceByAssociation = (association, associationId) => __awaiter(this, void 0, void 0, function* () {
133
+ const invalidateModelInstanceByAssociation = (hook, association, associationId) => newrelic_1.default.startSegment('invalidateModelInstanceByAssociation', true, () => __awaiter(this, void 0, void 0, function* () {
105
134
  const dependentInstancesKeys = yield cache.getClient().smembersAsync(generateDependencyKey(modelOptions.name, association, associationId));
106
- this.debug('Invalidating dependent instances', { dependentInstancesKeys });
135
+ this.debug(`Invalidating dependent instances (triggered by ${hook})`, { dependentInstancesKeys });
107
136
  const removeMulti = cache.getClient().multi();
108
137
  const removeMultiAsync = util_1.promisify(removeMulti.exec).bind(removeMulti);
109
138
  const dependenciesToRemove = yield Promise.all(dependentInstancesKeys.map((instanceKey) => __awaiter(this, void 0, void 0, function* () {
@@ -116,16 +145,21 @@ class SequelizeAdapter {
116
145
  removeMulti.del(instanceKey);
117
146
  return dependencyKeys;
118
147
  })));
119
- this.debug('Removing dependencies', { dependentInstancesKeys, dependenciesToRemove });
148
+ this.debug(`Removing dependencies (triggered by ${hook})`, { dependentInstancesKeys, dependenciesToRemove });
120
149
  return removeMultiAsync();
121
- });
150
+ }));
122
151
  const model = this.getModel(modelOptions.name);
123
- INVALIDATION_HOOKS.map(hook => model.addHook(hook, (instance, options) => handleTransactionHook(instance, options, instance => invalidateModelInstance(instance))));
152
+ INVALIDATION_HOOKS.map(hook => model.addHook(hook, (instance, options) => handleTransactionHook(instance, options, instance => invalidateModelInstance(hook, instance))));
124
153
  BULK_HOOKS.map(hook => model.addHook(hook, options => options.individualHook = true));
154
+ modelOptions.associations = this.getModelDependencies(modelOptions.name);
155
+ this.debug(`Adding Invalidations Hooks to ${modelOptions.name}'s associations`, { associations: modelOptions.associations });
125
156
  modelOptions.associations.map((associationOptions) => {
126
157
  const associationModel = this.getModel(associationOptions.name);
127
- INVALIDATION_HOOKS.map(hook => associationModel.addHook(hook, (instance, options) => handleTransactionHook(instance, options, associationInstance => invalidateModelInstanceByAssociation(associationOptions.name, associationInstance[associationOptions.accessKey || 'id']))));
128
- BULK_HOOKS.map(hook => associationModel.addHook(hook, options => options.individualHook = true));
158
+ INVALIDATION_HOOKS.map(hook => associationModel.addHook(hook, (instance, options) => handleTransactionHook(instance, options, associationInstance => invalidateModelInstanceByAssociation(hook, associationOptions.name, associationInstance[associationOptions.accessKey]))));
159
+ if (associationOptions.innerAssociation) {
160
+ const innerAssociationModel = this.getModel(associationOptions.innerAssociation.name);
161
+ INVALIDATION_HOOKS.map(hook => innerAssociationModel.addHook(hook, (instance, options) => handleTransactionHook(instance, options, innerAssociationInstance => invalidateModelInstanceByAssociation(hook, associationOptions.innerAssociation.name, innerAssociationInstance[associationOptions.innerAssociation.accessKey]))));
162
+ }
129
163
  });
130
164
  }
131
165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/matmon",
3
- "version": "2.0.1",
3
+ "version": "2.0.4-beta-1",
4
4
  "description": "manage cache",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -32,6 +32,7 @@
32
32
  "bluebird": "^3.7.2",
33
33
  "dotenv": "^8.2.0",
34
34
  "lru-cache": "^6.0.0",
35
+ "newrelic": "^7.5.2",
35
36
  "redis": "^3.0.2",
36
37
  "redis-lock": "^0.1.4",
37
38
  "sequelize": "^6.6.2",