@routr/location 2.0.5-alpha.2 → 2.0.5-alpha.3

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.
@@ -1,15 +1,4 @@
1
1
  "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
3
  if (k2 === undefined) k2 = k;
15
4
  Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
@@ -52,41 +41,50 @@ exports.getConfig = void 0;
52
41
  * See the License for the specific language governing permissions and
53
42
  * limitations under the License.
54
43
  */
55
- var types_1 = require("../types");
56
- var fs_1 = __importDefault(require("fs"));
57
- var schema_1 = require("./schema");
58
- var ajv_1 = __importDefault(require("ajv"));
59
- var E = __importStar(require("fp-ts/Either"));
60
- var errors_1 = require("../errors");
61
- var ajv = new ajv_1.default();
62
- var validate = ajv.compile(schema_1.schema);
63
- var getConfig = function (path) {
64
- var c = JSON.parse(fs_1.default.readFileSync(path, "utf8"));
65
- if (!validate(__assign({}, c))) {
44
+ const types_1 = require("../types");
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const schema_1 = require("./schema");
47
+ const ajv_1 = __importDefault(require("ajv"));
48
+ const E = __importStar(require("fp-ts/Either"));
49
+ const errors_1 = require("../errors");
50
+ const ajv = new ajv_1.default();
51
+ const validate = ajv.compile(schema_1.schema);
52
+ const hasBadCombiniation = (backends) => backends.some((b) => b.balancingAlgorithm === types_1.LB_ALGORITHM.ROUND_ROBIN && b.sessionAffinity);
53
+ const hasBadAlgorithm = (backends) => backends.some((b) => b.balancingAlgorithm !== types_1.LB_ALGORITHM.ROUND_ROBIN
54
+ && b.balancingAlgorithm !== types_1.LB_ALGORITHM.LEAST_SESSIONS);
55
+ const getConfig = (path) => {
56
+ var _a, _b, _c;
57
+ const c = JSON.parse(fs_1.default.readFileSync(path, "utf8"));
58
+ if (!validate(Object.assign({}, c))) {
66
59
  return E.left(new errors_1.InvalidSchemaConfiguration(JSON.stringify(validate.errors[0].message)));
67
60
  }
68
- var config = c.spec;
61
+ const config = c.spec;
69
62
  if (config.backends) {
70
- config.backends.map(function (b) {
63
+ config.backends.map((b) => {
71
64
  // Setting round-robin by default
72
65
  b.balancingAlgorithm = b.balancingAlgorithm
73
66
  ? b.balancingAlgorithm : types_1.LB_ALGORITHM.ROUND_ROBIN;
74
67
  return b;
75
68
  });
76
- var hasBadCombiniation = config.backends.some(function (b) {
77
- return b.balancingAlgorithm === types_1.LB_ALGORITHM.ROUND_ROBIN && b.sessionAffinity;
78
- });
79
- var hasBadAlgorithm = config.backends.some(function (b) {
80
- return b.balancingAlgorithm !== types_1.LB_ALGORITHM.ROUND_ROBIN
81
- && b.balancingAlgorithm !== types_1.LB_ALGORITHM.LEAST_SESSIONS;
82
- });
83
- if (hasBadCombiniation) {
69
+ if (hasBadCombiniation(config.backends)) {
84
70
  return E.left(new errors_1.BadAlgorithmAndAffinityCombination());
85
71
  }
86
- if (hasBadAlgorithm) {
72
+ if (hasBadAlgorithm(config.backends)) {
87
73
  return E.left(new errors_1.InvalidLoadBalancerAlgorithm());
88
74
  }
89
75
  }
76
+ if (!((_a = config === null || config === void 0 ? void 0 : config.cache) === null || _a === void 0 ? void 0 : _a.provider)) {
77
+ config.cache = {
78
+ provider: types_1.CACHE_PROVIDER.MEMORY
79
+ };
80
+ }
81
+ if (((_b = config === null || config === void 0 ? void 0 : config.cache) === null || _b === void 0 ? void 0 : _b.provider) === types_1.CACHE_PROVIDER.REDIS &&
82
+ !((_c = config === null || config === void 0 ? void 0 : config.cache) === null || _c === void 0 ? void 0 : _c.parameters)) {
83
+ config.cache = {
84
+ provider: types_1.CACHE_PROVIDER.REDIS,
85
+ parameters: "host=localhost,port=6379"
86
+ };
87
+ }
90
88
  return E.right(config);
91
89
  };
92
90
  exports.getConfig = getConfig;
@@ -22,6 +22,18 @@ export declare const schema: {
22
22
  description: string;
23
23
  type: string;
24
24
  };
25
+ cache: {
26
+ type: string;
27
+ properties: {
28
+ provider: {
29
+ enum: string[];
30
+ };
31
+ parameters: {
32
+ type: string;
33
+ };
34
+ };
35
+ required: string[];
36
+ };
25
37
  backends: {
26
38
  description: string;
27
39
  type: string;
@@ -43,6 +43,18 @@ exports.schema = {
43
43
  "description": "Ipv4 interface to accept request on",
44
44
  "type": "string"
45
45
  },
46
+ "cache": {
47
+ "type": "object",
48
+ "properties": {
49
+ "provider": {
50
+ "enum": ["memory", "redis"]
51
+ },
52
+ "parameters": {
53
+ "type": "string"
54
+ },
55
+ },
56
+ "required": ["provider"]
57
+ },
46
58
  "backends": {
47
59
  "description": "Optional SIP backends",
48
60
  "type": "array",
@@ -54,7 +66,7 @@ exports.schema = {
54
66
  "type": "string"
55
67
  },
56
68
  "balancingAlgorithm": {
57
- "enum": ["ROUND_ROBIN", "LEAST_SESSIONS"]
69
+ "enum": ["round-robin", "least-sessions"]
58
70
  },
59
71
  "sessionAffinity": {
60
72
  "description": "Optional session affinity",
package/dist/errors.js CHANGED
@@ -1,19 +1,4 @@
1
1
  "use strict";
2
- var __extends = (this && this.__extends) || (function () {
3
- var extendStatics = function (d, b) {
4
- extendStatics = Object.setPrototypeOf ||
5
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
- return extendStatics(d, b);
8
- };
9
- return function (d, b) {
10
- if (typeof b !== "function" && b !== null)
11
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
- extendStatics(d, b);
13
- function __() { this.constructor = d; }
14
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
- };
16
- })();
17
2
  Object.defineProperty(exports, "__esModule", { value: true });
18
3
  exports.InvalidLoadBalancerAlgorithm = exports.BadAlgorithmAndAffinityCombination = exports.InvalidSchemaConfiguration = exports.NotRoutesFoundForAOR = exports.UnsupportedSchema = void 0;
19
4
  /*
@@ -34,58 +19,43 @@ exports.InvalidLoadBalancerAlgorithm = exports.BadAlgorithmAndAffinityCombinatio
34
19
  * See the License for the specific language governing permissions and
35
20
  * limitations under the License.
36
21
  */
37
- var grpc = require("@grpc/grpc-js");
38
- var UnsupportedSchema = /** @class */ (function (_super) {
39
- __extends(UnsupportedSchema, _super);
40
- function UnsupportedSchema(aor) {
41
- var _this = _super.call(this, "aor " + aor + " has an invalid schema, only \"sip:\" or \"backend:\" are allowed") || this;
42
- _this.code = grpc.status.INVALID_ARGUMENT;
22
+ const grpc = require("@grpc/grpc-js");
23
+ class UnsupportedSchema extends Error {
24
+ constructor(aor) {
25
+ super(`aor ${aor} has an invalid schema, only "sip:" or "backend:" are allowed`);
26
+ this.code = grpc.status.INVALID_ARGUMENT;
43
27
  // Set the prototype explicitly.
44
- Object.setPrototypeOf(_this, UnsupportedSchema.prototype);
45
- return _this;
28
+ Object.setPrototypeOf(this, UnsupportedSchema.prototype);
46
29
  }
47
- return UnsupportedSchema;
48
- }(Error));
30
+ }
49
31
  exports.UnsupportedSchema = UnsupportedSchema;
50
- var NotRoutesFoundForAOR = /** @class */ (function (_super) {
51
- __extends(NotRoutesFoundForAOR, _super);
52
- function NotRoutesFoundForAOR(aor) {
53
- var _this = _super.call(this, "no routes found for aor " + aor) || this;
54
- _this.code = grpc.status.NOT_FOUND;
55
- Object.setPrototypeOf(_this, NotRoutesFoundForAOR.prototype);
56
- return _this;
32
+ class NotRoutesFoundForAOR extends Error {
33
+ constructor(aor) {
34
+ super(`no routes found for aor ${aor}`);
35
+ this.code = grpc.status.NOT_FOUND;
36
+ Object.setPrototypeOf(this, NotRoutesFoundForAOR.prototype);
57
37
  }
58
- return NotRoutesFoundForAOR;
59
- }(Error));
38
+ }
60
39
  exports.NotRoutesFoundForAOR = NotRoutesFoundForAOR;
61
- var InvalidSchemaConfiguration = /** @class */ (function (_super) {
62
- __extends(InvalidSchemaConfiguration, _super);
63
- function InvalidSchemaConfiguration(msg) {
64
- var _this = _super.call(this, msg) || this;
65
- _this.code = grpc.status.INVALID_ARGUMENT;
66
- Object.setPrototypeOf(_this, InvalidSchemaConfiguration.prototype);
67
- return _this;
40
+ class InvalidSchemaConfiguration extends Error {
41
+ constructor(msg) {
42
+ super(msg);
43
+ this.code = grpc.status.INVALID_ARGUMENT;
44
+ Object.setPrototypeOf(this, InvalidSchemaConfiguration.prototype);
68
45
  }
69
- return InvalidSchemaConfiguration;
70
- }(Error));
46
+ }
71
47
  exports.InvalidSchemaConfiguration = InvalidSchemaConfiguration;
72
- var BadAlgorithmAndAffinityCombination = /** @class */ (function (_super) {
73
- __extends(BadAlgorithmAndAffinityCombination, _super);
74
- function BadAlgorithmAndAffinityCombination() {
75
- var _this = _super.call(this, "session affinity can not be combined with round-robin load balancing") || this;
76
- Object.setPrototypeOf(_this, BadAlgorithmAndAffinityCombination.prototype);
77
- return _this;
48
+ class BadAlgorithmAndAffinityCombination extends Error {
49
+ constructor() {
50
+ super("session affinity can not be combined with round-robin load balancing");
51
+ Object.setPrototypeOf(this, BadAlgorithmAndAffinityCombination.prototype);
78
52
  }
79
- return BadAlgorithmAndAffinityCombination;
80
- }(Error));
53
+ }
81
54
  exports.BadAlgorithmAndAffinityCombination = BadAlgorithmAndAffinityCombination;
82
- var InvalidLoadBalancerAlgorithm = /** @class */ (function (_super) {
83
- __extends(InvalidLoadBalancerAlgorithm, _super);
84
- function InvalidLoadBalancerAlgorithm() {
85
- var _this = _super.call(this, "found invalid load balancing algorithm. must be round-robin or least-sessions") || this;
86
- Object.setPrototypeOf(_this, BadAlgorithmAndAffinityCombination.prototype);
87
- return _this;
55
+ class InvalidLoadBalancerAlgorithm extends Error {
56
+ constructor() {
57
+ super("found invalid load balancing algorithm. must be round-robin or least-sessions");
58
+ Object.setPrototypeOf(this, BadAlgorithmAndAffinityCombination.prototype);
88
59
  }
89
- return InvalidLoadBalancerAlgorithm;
90
- }(Error));
60
+ }
91
61
  exports.InvalidLoadBalancerAlgorithm = InvalidLoadBalancerAlgorithm;
package/dist/locator.js CHANGED
@@ -8,33 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __generator = (this && this.__generator) || function (thisArg, body) {
12
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
- function verb(n) { return function (v) { return step([n, v]); }; }
15
- function step(op) {
16
- if (f) throw new TypeError("Generator is already executing.");
17
- while (_) try {
18
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
- if (y = 0, t) op = [op[0] & 2, t.value];
20
- switch (op[0]) {
21
- case 0: case 1: t = op; break;
22
- case 4: _.label++; return { value: op[1], done: false };
23
- case 5: _.label++; y = op[1]; op = [0]; continue;
24
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
- default:
26
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
- if (t[2]) _.ops.pop();
31
- _.trys.pop(); continue;
32
- }
33
- op = body.call(thisArg, _);
34
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
- }
37
- };
38
11
  Object.defineProperty(exports, "__esModule", { value: true });
39
12
  /*
40
13
  * Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
@@ -54,99 +27,75 @@ Object.defineProperty(exports, "__esModule", { value: true });
54
27
  * See the License for the specific language governing permissions and
55
28
  * limitations under the License.
56
29
  */
57
- var errors_1 = require("./errors");
58
- var types_1 = require("./types");
59
- var utils_1 = require("./utils");
60
- var Locator = /** @class */ (function () {
30
+ const errors_1 = require("./errors");
31
+ const types_1 = require("./types");
32
+ const utils_1 = require("./utils");
33
+ class Locator {
61
34
  // Should fail if any backend has sessionAffinity and round-robin
62
- function Locator(store, backends) {
63
- var _this = this;
64
- if (backends === void 0) { backends = new Map(); }
35
+ constructor(store, backends = new Map()) {
65
36
  this.store = store;
66
37
  this.backends = backends;
67
38
  this.rrCount = new Map();
68
39
  this.affinityStore = new Map();
69
- this.backends.forEach(function (value, key) { return _this.rrCount.set(key, 0); });
40
+ this.backends.forEach((value, key) => this.rrCount.set(key, 0));
70
41
  }
71
- Locator.prototype.addRoute = function (request) {
42
+ addRoute(request) {
72
43
  if (!request.aor.startsWith('sip:') &&
73
44
  !request.aor.startsWith('backend:')) {
74
45
  throw new errors_1.UnsupportedSchema(request.aor);
75
46
  }
76
47
  return this.store.put(request.aor, request.route);
77
- };
78
- Locator.prototype.findRoutes = function (request) {
79
- return __awaiter(this, void 0, void 0, function () {
80
- var routes, _a, backend, _b, _c;
81
- return __generator(this, function (_d) {
82
- switch (_d.label) {
83
- case 0:
84
- if (!request.labels) return [3 /*break*/, 2];
85
- return [4 /*yield*/, this.store.get(request.aor)];
86
- case 1:
87
- _a = (_d.sent())
88
- .filter(utils_1.filterOnlyMatchingLabels(request.labels));
89
- return [3 /*break*/, 4];
90
- case 2: return [4 /*yield*/, this.store.get(request.aor)];
91
- case 3:
92
- _a = (_d.sent()) || [];
93
- _d.label = 4;
94
- case 4:
95
- routes = _a;
96
- if (!request.aor.startsWith('sip:')) return [3 /*break*/, 5];
97
- return [2 /*return*/, routes];
98
- case 5:
99
- if (!request.aor.startsWith('backend:')) return [3 /*break*/, 9];
100
- backend = this.backends.get(request.aor);
101
- if (!backend) {
102
- throw new errors_1.NotRoutesFoundForAOR(request.aor);
103
- }
104
- if (!!utils_1.hasAffinitySession(backend)) return [3 /*break*/, 6];
105
- _b = [this.next(routes, backend)];
106
- return [3 /*break*/, 8];
107
- case 6:
108
- _c = this.nextWithAffinity;
109
- return [4 /*yield*/, routes];
110
- case 7:
111
- _b = [_c.apply(this, [_d.sent(), backend.sessionAffinity.ref])];
112
- _d.label = 8;
113
- case 8:
114
- // If it has not affinity sesssion then get next
115
- return [2 /*return*/, _b];
116
- case 9: throw new errors_1.UnsupportedSchema(request.aor);
48
+ }
49
+ findRoutes(request) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ const routes = request.labels
52
+ ? (yield this.store.get(request.aor))
53
+ .filter(utils_1.filterOnlyMatchingLabels(request.labels))
54
+ : (yield this.store.get(request.aor)) || [];
55
+ if (request.aor.startsWith('sip:')) {
56
+ return routes;
57
+ }
58
+ else if (request.aor.startsWith('backend:')) {
59
+ const backend = this.backends.get(request.aor);
60
+ if (!backend) {
61
+ throw new errors_1.NotRoutesFoundForAOR(request.aor);
117
62
  }
118
- });
63
+ // If it has not affinity sesssion then get next
64
+ return !utils_1.hasAffinitySession(backend)
65
+ ? [this.next(routes, backend)]
66
+ : [this.nextWithAffinity(yield routes, backend.sessionAffinity.ref)];
67
+ }
68
+ throw new errors_1.UnsupportedSchema(request.aor);
119
69
  });
120
- };
121
- Locator.prototype.removeRoutes = function (request) {
70
+ }
71
+ removeRoutes(request) {
122
72
  return this.store.delete(request.aor);
123
- };
124
- Locator.prototype.next = function (routes, backend) {
73
+ }
74
+ next(routes, backend) {
125
75
  if (backend.balancingAlgorithm === types_1.LB_ALGORITHM.LEAST_SESSIONS) {
126
- return routes.sort(function (r1, r2) { return r1.sessionCount - r2.sessionCount; })[0];
76
+ return routes.sort((r1, r2) => r1.sessionCount - r2.sessionCount)[0];
127
77
  }
128
78
  // Continues using round-robin
129
- var nextPosition = this.rrCount.get("backend:" + backend.ref);
130
- var result = routes[nextPosition];
79
+ const nextPosition = this.rrCount.get(`backend:${backend.ref}`);
80
+ const result = routes[nextPosition];
131
81
  if (nextPosition >= (routes.length - 1)) {
132
82
  // Restarting round-robin counter
133
- this.rrCount.set("backend:" + backend.ref, 0);
83
+ this.rrCount.set(`backend:${backend.ref}`, 0);
134
84
  }
135
85
  else {
136
- this.rrCount.set("backend:" + backend.ref, nextPosition + 1);
86
+ this.rrCount.set(`backend:${backend.ref}`, nextPosition + 1);
137
87
  }
138
88
  return result;
139
- };
89
+ }
140
90
  // Backend with session affinity does not support round-robin
141
- Locator.prototype.nextWithAffinity = function (routes, sessionAffinityRef) {
142
- var route = this.affinityStore.get(sessionAffinityRef);
91
+ nextWithAffinity(routes, sessionAffinityRef) {
92
+ let route = this.affinityStore.get(sessionAffinityRef);
143
93
  if (route) {
144
94
  return route;
145
95
  }
146
- route = routes.sort(function (r1, r2) { return r1.sessionCount - r2.sessionCount; })[0];
96
+ route = routes.sort((r1, r2) => r1.sessionCount - r2.sessionCount)[0];
147
97
  this.affinityStore.set(sessionAffinityRef, route);
148
98
  return route;
149
- };
150
- return Locator;
151
- }());
99
+ }
100
+ }
152
101
  exports.default = Locator;
@@ -1,49 +1,27 @@
1
1
  "use strict";
2
- var __read = (this && this.__read) || function (o, n) {
3
- var m = typeof Symbol === "function" && o[Symbol.iterator];
4
- if (!m) return o;
5
- var i = m.call(o), r, ar = [], e;
6
- try {
7
- while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
8
- }
9
- catch (error) { e = { error: error }; }
10
- finally {
11
- try {
12
- if (r && !r.done && (m = i["return"])) m.call(i);
13
- }
14
- finally { if (e) throw e.error; }
15
- }
16
- return ar;
17
- };
18
- var __spreadArray = (this && this.__spreadArray) || function (to, from) {
19
- for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
20
- to[j] = from[i];
21
- return to;
22
- };
23
2
  Object.defineProperty(exports, "__esModule", { value: true });
24
- var utils_1 = require("./utils");
25
- var MAX_CYCLES_BEFORE_CLEANUP = 10000;
26
- var MemoryStore = /** @class */ (function () {
27
- function MemoryStore(maxCyclesBeforeCleanup) {
28
- if (maxCyclesBeforeCleanup === void 0) { maxCyclesBeforeCleanup = MAX_CYCLES_BEFORE_CLEANUP; }
3
+ const utils_1 = require("./utils");
4
+ const MAX_CYCLES_BEFORE_CLEANUP = 10000;
5
+ class MemoryStore {
6
+ constructor(maxCyclesBeforeCleanup = MAX_CYCLES_BEFORE_CLEANUP) {
29
7
  this.collections = new Map();
30
8
  this.maxCyclesBeforeCleanup = maxCyclesBeforeCleanup;
31
9
  }
32
- MemoryStore.prototype.put = function (key, route) {
33
- var routes = __spreadArray([], __read(this.collections.get(key) || []));
10
+ put(key, route) {
11
+ const routes = [...this.collections.get(key) || []];
34
12
  // Avoids duplicate or expired routes
35
- var filteredRoutes = routes
13
+ const filteredRoutes = routes
36
14
  .filter(utils_1.expiredFilter)
37
- .filter(function (r) { return utils_1.duplicateFilter(r, route); });
15
+ .filter(r => utils_1.duplicateFilter(r, route));
38
16
  if (filteredRoutes.length > 0) {
39
- this.collections.set(key, __spreadArray([route], __read(filteredRoutes)));
17
+ this.collections.set(key, [route, ...filteredRoutes]);
40
18
  }
41
19
  else {
42
20
  this.collections.set(key, [route]);
43
21
  }
44
22
  return;
45
- };
46
- MemoryStore.prototype.get = function (key) {
23
+ }
24
+ get(key) {
47
25
  // Cleanup every so often to avoid memory build up
48
26
  this.cleanupCount++;
49
27
  if (this.cleanupCount >= this.maxCyclesBeforeCleanup) {
@@ -51,27 +29,25 @@ var MemoryStore = /** @class */ (function () {
51
29
  this.cleanup();
52
30
  }
53
31
  return Promise.resolve(this.collections.get(key) || []);
54
- };
55
- MemoryStore.prototype.delete = function (key) {
32
+ }
33
+ delete(key) {
56
34
  this.collections.delete(key);
57
35
  return;
58
- };
59
- MemoryStore.prototype.size = function () {
36
+ }
37
+ size() {
60
38
  return this.collections.size;
61
- };
62
- MemoryStore.prototype.cleanup = function () {
63
- var _this = this;
39
+ }
40
+ cleanup() {
64
41
  // Remove any expired route from collection
65
- this.collections.forEach(function (routes, key) {
66
- var filteredRoutes = routes.filter(utils_1.expiredFilter);
42
+ this.collections.forEach((routes, key) => {
43
+ const filteredRoutes = routes.filter(utils_1.expiredFilter);
67
44
  if (filteredRoutes.length > 0) {
68
- _this.collections.set(key, filteredRoutes);
45
+ this.collections.set(key, filteredRoutes);
69
46
  }
70
47
  else {
71
- _this.collections.delete(key);
48
+ this.collections.delete(key);
72
49
  }
73
50
  });
74
- };
75
- return MemoryStore;
76
- }());
51
+ }
52
+ }
77
53
  exports.default = MemoryStore;
@@ -0,0 +1,8 @@
1
+ import { ILocatorStore, RedisStoreConfig, Route } from "./types";
2
+ export default class RedisStore implements ILocatorStore {
3
+ client: any;
4
+ constructor(config?: RedisStoreConfig);
5
+ put(key: string, route: Route): Promise<void>;
6
+ get(key: string): Promise<Route[]>;
7
+ delete(key: string): Promise<void>;
8
+ }
@@ -0,0 +1,87 @@
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 __asyncValues = (this && this.__asyncValues) || function (o) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var m = o[Symbol.asyncIterator], i;
14
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
15
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
16
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
17
+ };
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const redis_1 = require("redis");
23
+ const utils_1 = require("./utils");
24
+ const logger_1 = __importDefault(require("@fonoster/logger"));
25
+ class RedisStore {
26
+ constructor(config) {
27
+ this.client = config ? redis_1.createClient({ url: utils_1.getUrlString(config) }) : redis_1.createClient();
28
+ this.client.connect();
29
+ this.client.on('error', (err) => logger_1.default.error('redis client error: ', err));
30
+ }
31
+ put(key, route) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ // Formatted the key to ensure it is unique
34
+ logger_1.default.verbose(`${key}:${route.user}${route.host}${route.port}`);
35
+ yield this.client.set(`${key}:${route.user}${route.host}${route.port}`, JSON.stringify(route), {
36
+ EX: route.expires
37
+ });
38
+ return;
39
+ });
40
+ }
41
+ get(key) {
42
+ var e_1, _a;
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ const routes = [];
45
+ try {
46
+ for (var _b = __asyncValues(yield this.client.scanIterator({
47
+ CURSOR: 0,
48
+ MATCH: `${key}:*`
49
+ })), _c; _c = yield _b.next(), !_c.done;) {
50
+ const k = _c.value;
51
+ routes.push(JSON.parse(yield this.client.get(k)));
52
+ }
53
+ }
54
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
55
+ finally {
56
+ try {
57
+ if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
58
+ }
59
+ finally { if (e_1) throw e_1.error; }
60
+ }
61
+ return routes;
62
+ });
63
+ }
64
+ delete(key) {
65
+ var e_2, _a;
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ try {
68
+ for (var _b = __asyncValues(yield this.client.scanIterator({
69
+ CURSOR: 0,
70
+ MATCH: `${key}:*`
71
+ })), _c; _c = yield _b.next(), !_c.done;) {
72
+ const k = _c.value;
73
+ yield this.client.del(k);
74
+ }
75
+ }
76
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
77
+ finally {
78
+ try {
79
+ if (_c && !_c.done && (_a = _b.return)) yield _a.call(_b);
80
+ }
81
+ finally { if (e_2) throw e_2.error; }
82
+ }
83
+ return;
84
+ });
85
+ }
86
+ }
87
+ exports.default = RedisStore;
package/dist/runner.js CHANGED
@@ -22,10 +22,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
22
22
  * See the License for the specific language governing permissions and
23
23
  * limitations under the License.
24
24
  */
25
- var logger_1 = __importDefault(require("@fonoster/logger"));
26
- var service_1 = __importDefault(require("./service"));
27
- var get_config_1 = require("./config/get_config");
28
- var result = get_config_1.getConfig(process.env.CONFIG_PATH);
25
+ const logger_1 = __importDefault(require("@fonoster/logger"));
26
+ const service_1 = __importDefault(require("./service"));
27
+ const get_config_1 = require("./config/get_config");
28
+ const result = get_config_1.getConfig(process.env.CONFIG_PATH);
29
29
  if (result._tag === 'Right') {
30
30
  service_1.default(result.right);
31
31
  }
package/dist/service.js CHANGED
@@ -3,12 +3,44 @@ 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
- var common_1 = require("@routr/common");
7
- var utils_1 = require("./utils");
8
- var locator_1 = __importDefault(require("./locator"));
9
- var memory_store_1 = __importDefault(require("./memory_store"));
6
+ /*
7
+ * Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
8
+ * http://github.com/fonoster/routr
9
+ *
10
+ * This file is part of Routr
11
+ *
12
+ * Licensed under the MIT License (the "License")
13
+ * you may not use this file except in compliance with
14
+ * the License. You may obtain a copy of the License at
15
+ *
16
+ * https://opensource.org/licenses/MIT
17
+ *
18
+ * Unless required by applicable law or agreed to in writing, software
19
+ * distributed under the License is distributed on an "AS IS" BASIS,
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ * See the License for the specific language governing permissions and
22
+ * limitations under the License.
23
+ */
24
+ const types_1 = require("./types");
25
+ const common_1 = require("@routr/common");
26
+ const utils_1 = require("./utils");
27
+ const locator_1 = __importDefault(require("./locator"));
28
+ const memory_store_1 = __importDefault(require("./memory_store"));
29
+ const redis_store_1 = __importDefault(require("./redis_store"));
30
+ const logger_1 = __importDefault(require("@fonoster/logger"));
31
+ const allowedParameters = ["host", "port", "username", "password", "secure"];
10
32
  function LocationService(config) {
11
- var bindAddr = config.bindAddr;
12
- common_1.createService(utils_1.getServiceInfo(bindAddr, new locator_1.default(new memory_store_1.default())));
33
+ const { bindAddr, cache } = config;
34
+ let store;
35
+ if (cache.provider === types_1.CACHE_PROVIDER.REDIS) {
36
+ store = new redis_store_1.default(utils_1.configFromString(cache.parameters, allowedParameters));
37
+ }
38
+ else {
39
+ store = new memory_store_1.default();
40
+ }
41
+ const backends = new Map();
42
+ config.backends.forEach(b => backends.set(`backend:${b.ref}`, b));
43
+ common_1.createService(utils_1.getServiceInfo(bindAddr, new locator_1.default(store, backends)));
44
+ logger_1.default.info(`using ${cache.provider} as cache provider`);
13
45
  }
14
46
  exports.default = LocationService;
package/dist/types.d.ts CHANGED
@@ -2,6 +2,10 @@ export declare enum LB_ALGORITHM {
2
2
  ROUND_ROBIN = "round-robin",
3
3
  LEAST_SESSIONS = "least-sessions"
4
4
  }
5
+ export declare enum CACHE_PROVIDER {
6
+ MEMORY = "memory",
7
+ REDIS = "redis"
8
+ }
5
9
  export interface ILocationService {
6
10
  addRoute(request: AddRouteRequest): Promise<void>;
7
11
  findRoutes(request: FindRoutesRequest): Promise<Route[]>;
@@ -49,4 +53,15 @@ export interface Backend {
49
53
  export interface LocationConfig {
50
54
  bindAddr: string;
51
55
  backends?: Backend[];
56
+ cache?: {
57
+ provider: CACHE_PROVIDER;
58
+ parameters: string;
59
+ };
60
+ }
61
+ export interface RedisStoreConfig {
62
+ username?: string;
63
+ password?: string;
64
+ host: string;
65
+ port: number;
66
+ secure?: boolean;
52
67
  }
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LB_ALGORITHM = void 0;
3
+ exports.CACHE_PROVIDER = exports.LB_ALGORITHM = void 0;
4
4
  /*
5
5
  * Copyright (C) 2022 by Fonoster Inc (https://fonoster.com)
6
6
  * http://github.com/fonoster/routr
@@ -24,3 +24,8 @@ var LB_ALGORITHM;
24
24
  LB_ALGORITHM["ROUND_ROBIN"] = "round-robin";
25
25
  LB_ALGORITHM["LEAST_SESSIONS"] = "least-sessions";
26
26
  })(LB_ALGORITHM = exports.LB_ALGORITHM || (exports.LB_ALGORITHM = {}));
27
+ var CACHE_PROVIDER;
28
+ (function (CACHE_PROVIDER) {
29
+ CACHE_PROVIDER["MEMORY"] = "memory";
30
+ CACHE_PROVIDER["REDIS"] = "redis";
31
+ })(CACHE_PROVIDER = exports.CACHE_PROVIDER || (exports.CACHE_PROVIDER = {}));
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Backend, ILocationService, Route } from "./types";
1
+ import { Backend, ILocationService, RedisStoreConfig, Route } from "./types";
2
2
  import { ServiceInfo } from "@routr/common";
3
3
  export declare const expiredFilter: (r: Route) => boolean;
4
4
  export declare const duplicateFilter: (r1: Route, r2: Route) => boolean;
@@ -7,3 +7,5 @@ export declare const compareArrays: (arr: string[], target: string[]) => boolean
7
7
  export declare const filterOnlyMatchingLabels: (requestLabels: Map<string, string>) => (route: Route) => boolean;
8
8
  export declare const hasAffinitySession: (backend: Backend) => boolean;
9
9
  export declare function getServiceInfo(bindAddr: string, locator: ILocationService): ServiceInfo;
10
+ export declare const configFromString: (params: string, allowedKeys: string[]) => Record<string, string | boolean>;
11
+ export declare const getUrlString: (config: RedisStoreConfig) => string;
package/dist/utils.js CHANGED
@@ -8,67 +8,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __generator = (this && this.__generator) || function (thisArg, body) {
12
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
- function verb(n) { return function (v) { return step([n, v]); }; }
15
- function step(op) {
16
- if (f) throw new TypeError("Generator is already executing.");
17
- while (_) try {
18
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
- if (y = 0, t) op = [op[0] & 2, t.value];
20
- switch (op[0]) {
21
- case 0: case 1: t = op; break;
22
- case 4: _.label++; return { value: op[1], done: false };
23
- case 5: _.label++; y = op[1]; op = [0]; continue;
24
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
- default:
26
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
- if (t[2]) _.ops.pop();
31
- _.trys.pop(); continue;
32
- }
33
- op = body.call(thisArg, _);
34
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
- }
37
- };
38
11
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getServiceInfo = exports.hasAffinitySession = exports.filterOnlyMatchingLabels = exports.compareArrays = exports.mergeKeyValue = exports.duplicateFilter = exports.expiredFilter = void 0;
40
- var common_1 = require("@routr/common");
41
- var errors_1 = require("./errors");
42
- var expiredFilter = function (r) {
43
- return (r.expires - (Date.now() - r.registeredOn) / 1000) > 0;
44
- };
12
+ exports.getUrlString = exports.configFromString = exports.getServiceInfo = exports.hasAffinitySession = exports.filterOnlyMatchingLabels = exports.compareArrays = exports.mergeKeyValue = exports.duplicateFilter = exports.expiredFilter = void 0;
13
+ const common_1 = require("@routr/common");
14
+ const errors_1 = require("./errors");
15
+ const expiredFilter = (r) => (r.expires - (Date.now() - r.registeredOn) / 1000) > 0;
45
16
  exports.expiredFilter = expiredFilter;
46
- var duplicateFilter = function (r1, r2) {
47
- return !(r1.host === r2.host && r1.port === r2.port);
48
- };
17
+ const duplicateFilter = (r1, r2) => !(r1.host === r2.host && r1.port === r2.port);
49
18
  exports.duplicateFilter = duplicateFilter;
50
- var mergeKeyValue = function (map) { return Array.from(map).map(function (l) { return l[0] + l[1]; }); };
19
+ const mergeKeyValue = (map) => Array.from(map).map(l => l[0] + l[1]);
51
20
  exports.mergeKeyValue = mergeKeyValue;
52
- var compareArrays = function (arr, target) {
53
- return target.every(function (v) { return arr.includes(v); }) && arr.length === target.length;
54
- };
21
+ const compareArrays = (arr, target) => target.every(v => arr.includes(v)) && arr.length === target.length;
55
22
  exports.compareArrays = compareArrays;
56
- var filterOnlyMatchingLabels = function (requestLabels) {
57
- return function (route) { return route.labels
58
- ? exports.compareArrays(exports.mergeKeyValue(requestLabels), exports.mergeKeyValue(route.labels))
59
- : false; };
60
- };
23
+ const filterOnlyMatchingLabels = (requestLabels) => (route) => route.labels
24
+ ? exports.compareArrays(exports.mergeKeyValue(requestLabels), exports.mergeKeyValue(route.labels))
25
+ : false;
61
26
  exports.filterOnlyMatchingLabels = filterOnlyMatchingLabels;
62
- var hasAffinitySession = function (backend) { var _a; return (_a = backend === null || backend === void 0 ? void 0 : backend.sessionAffinity) === null || _a === void 0 ? void 0 : _a.enabled; };
27
+ const hasAffinitySession = (backend) => { var _a; return (_a = backend === null || backend === void 0 ? void 0 : backend.sessionAffinity) === null || _a === void 0 ? void 0 : _a.enabled; };
63
28
  exports.hasAffinitySession = hasAffinitySession;
64
29
  function getServiceInfo(bindAddr, locator) {
65
- var _this = this;
66
30
  return {
67
31
  name: "location",
68
- bindAddr: bindAddr,
32
+ bindAddr,
69
33
  service: common_1.LOCATION_OBJECT_PROTO.Location.service,
70
34
  handlers: {
71
- addRoute: function (call, callback) {
35
+ addRoute: (call, callback) => {
72
36
  try {
73
37
  locator.addRoute(call.request);
74
38
  callback(null, {});
@@ -77,53 +41,59 @@ function getServiceInfo(bindAddr, locator) {
77
41
  callback(e, null);
78
42
  }
79
43
  },
80
- findRoutes: function (call, callback) { return __awaiter(_this, void 0, void 0, function () {
81
- var routes, e_1;
82
- return __generator(this, function (_a) {
83
- switch (_a.label) {
84
- case 0:
85
- _a.trys.push([0, 2, , 3]);
86
- return [4 /*yield*/, locator.findRoutes(call.request)];
87
- case 1:
88
- routes = _a.sent();
89
- if (routes.length == 0)
90
- throw new errors_1.NotRoutesFoundForAOR(call.request.aor);
91
- callback(null, {
92
- routes: routes
93
- });
94
- return [3 /*break*/, 3];
95
- case 2:
96
- e_1 = _a.sent();
97
- callback(e_1, null);
98
- return [3 /*break*/, 3];
99
- case 3: return [2 /*return*/];
100
- }
101
- });
102
- }); },
103
- removeRoutes: function (call, callback) { return __awaiter(_this, void 0, void 0, function () {
104
- var _a, _b, e_2;
105
- var _c;
106
- return __generator(this, function (_d) {
107
- switch (_d.label) {
108
- case 0:
109
- _d.trys.push([0, 2, , 3]);
110
- _a = callback;
111
- _b = [null];
112
- _c = {};
113
- return [4 /*yield*/, locator.findRoutes(call.request)];
114
- case 1:
115
- _a.apply(void 0, _b.concat([(_c.routes = _d.sent(),
116
- _c)]));
117
- return [3 /*break*/, 3];
118
- case 2:
119
- e_2 = _d.sent();
120
- callback(e_2, null);
121
- return [3 /*break*/, 3];
122
- case 3: return [2 /*return*/];
123
- }
124
- });
125
- }); }
44
+ findRoutes: (call, callback) => __awaiter(this, void 0, void 0, function* () {
45
+ try {
46
+ const routes = yield locator.findRoutes(call.request);
47
+ if (routes.length == 0)
48
+ throw new errors_1.NotRoutesFoundForAOR(call.request.aor);
49
+ callback(null, {
50
+ routes: routes
51
+ });
52
+ }
53
+ catch (e) {
54
+ callback(e, null);
55
+ }
56
+ }),
57
+ removeRoutes: (call, callback) => __awaiter(this, void 0, void 0, function* () {
58
+ try {
59
+ callback(null, {
60
+ routes: yield locator.findRoutes(call.request)
61
+ });
62
+ }
63
+ catch (e) {
64
+ callback(e, null);
65
+ }
66
+ })
126
67
  }
127
68
  };
128
69
  }
129
70
  exports.getServiceInfo = getServiceInfo;
71
+ const configFromString = (params, allowedKeys) => {
72
+ if (params.length === 0)
73
+ return {};
74
+ const parameters = {};
75
+ params.split(',').forEach(par => {
76
+ try {
77
+ const key = par.split('=')[0];
78
+ const value = par.split('=')[1];
79
+ if (allowedKeys.indexOf(key) === -1) {
80
+ throw `invalid parameter: ${key}`;
81
+ }
82
+ else {
83
+ parameters[key] = value === "true" ? true : value;
84
+ }
85
+ }
86
+ catch (e) {
87
+ throw new Error(`invalid parameters string: ${params}; should be something like 'host=localhost,port=6379'`);
88
+ }
89
+ });
90
+ return parameters;
91
+ };
92
+ exports.configFromString = configFromString;
93
+ const getUrlString = (config) => {
94
+ return `redis${config.secure ? 's' : ''}`
95
+ + `://${config.username ? config.username : ''}`
96
+ + `${config.password ? ':' + config.password + '@' : ''}`
97
+ + `${config.host}:${config.port}`;
98
+ };
99
+ exports.getUrlString = getUrlString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routr/location",
3
- "version": "2.0.5-alpha.2",
3
+ "version": "2.0.5-alpha.3",
4
4
  "description": "Creates and maintains a list of routes to all registered endpoints",
5
5
  "author": "Pedro Sanders <psanders@fonoster.com>",
6
6
  "homepage": "https://github.com/fonoster/routr#readme",
@@ -25,9 +25,10 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@routr/common": "^2.0.5-alpha.2",
28
+ "@routr/common": "^2.0.5-alpha.3",
29
29
  "ajv": "^6.12.6",
30
- "fp-ts": "^2.11.8"
30
+ "fp-ts": "^2.11.8",
31
+ "redis": "^4.0.4"
31
32
  },
32
33
  "repository": {
33
34
  "type": "git",
@@ -36,5 +37,5 @@
36
37
  "bugs": {
37
38
  "url": "https://github.com/fonoster/routr/issues"
38
39
  },
39
- "gitHead": "3570106be9a96cb6672de4a850a377bfa7d9bb3e"
40
+ "gitHead": "7d942466133a03f4bcfe0dc100f1a922b3ed14ad"
40
41
  }