index.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. 'use strict';
  2. const is = require('is-type-of');
  3. const symbols = require('./symbol');
  4. const logger = require('./default_logger');
  5. const transcode = require('./default_transcode');
  6. const SingleClient = require('./wrapper/single');
  7. const ClusterClient = require('./wrapper/cluster');
  8. const { formatKey } = require('./utils');
  9. const defaultOptions = {
  10. port: parseInt(process.env.NODE_CLUSTER_CLIENT_PORT) || 7777,
  11. singleMode: process.env.NODE_CLUSTER_CLIENT_SINGLE_MODE === '1',
  12. maxWaitTime: 30000,
  13. connectTimeout: parseInt(process.env.NODE_CLUSTER_CLIENT_CONNECT_TIMEOUT) || 10000,
  14. responseTimeout: 3000,
  15. heartbeatInterval: 20000,
  16. autoGenerate: true,
  17. isBroadcast: true,
  18. logger,
  19. transcode,
  20. formatKey,
  21. };
  22. const autoGenerateMethods = [
  23. 'subscribe',
  24. 'unSubscribe',
  25. 'publish',
  26. 'close',
  27. ];
  28. class ClientGenerator {
  29. /**
  30. * Cluster Client Generator
  31. *
  32. * @param {Function} clientClass - the client class
  33. * @param {Object} options
  34. * - {Number} responseTimeout - response timeout, default is 3000
  35. * - {Boolean} autoGenerate - whether generate delegate rule automatically, default is true
  36. * - {Boolean} isBroadcast - whether broadcast subscrption result to all followers or just one, default is true
  37. * - {Logger} logger - log instance
  38. * - {Transcode} [transcode|JSON.stringify/parse]
  39. * - {Function} encode - custom serialize method
  40. * - {Function} decode - custom deserialize method
  41. * - {Boolean} [isLeader|null] - specify whether current instance is leader
  42. * - {Number} [maxWaitTime|30000] - leader startup max time (ONLY effective on isLeader is true)
  43. * @constructor
  44. */
  45. constructor(clientClass, options) {
  46. this._clientClass = clientClass;
  47. this._options = Object.assign({
  48. name: clientClass.prototype.constructor.name,
  49. }, defaultOptions, options);
  50. // wrapper descptions
  51. this._descriptors = new Map();
  52. }
  53. /**
  54. * override the property
  55. *
  56. * @param {String} name - property name
  57. * @param {Object} value - property value
  58. * @return {ClientGenerator} self
  59. */
  60. override(name, value) {
  61. this._descriptors.set(name, {
  62. type: 'override',
  63. value,
  64. });
  65. return this;
  66. }
  67. /**
  68. * delegate methods
  69. *
  70. * @param {String} from - method name
  71. * @param {String} to - delegate to subscribe|publish|invoke
  72. * @return {ClientGenerator} self
  73. */
  74. delegate(from, to) {
  75. to = to || 'invoke';
  76. this._descriptors.set(from, {
  77. type: 'delegate',
  78. to,
  79. });
  80. return this;
  81. }
  82. /**
  83. * create cluster client instance
  84. *
  85. * @return {Object} instance
  86. */
  87. create(...args) {
  88. const clientClass = this._clientClass;
  89. const proto = clientClass.prototype;
  90. const descriptors = this._descriptors;
  91. // auto generate description
  92. if (this._options.autoGenerate) {
  93. this._generateDescriptors();
  94. }
  95. function createRealClient() {
  96. return Reflect.construct(clientClass, args);
  97. }
  98. const ClientWrapper = this._options.singleMode ? SingleClient : ClusterClient;
  99. const client = new ClientWrapper(Object.assign({
  100. createRealClient,
  101. descriptors: this._descriptors,
  102. }, this._options));
  103. for (const name of descriptors.keys()) {
  104. let value;
  105. const descriptor = descriptors.get(name);
  106. switch (descriptor.type) {
  107. case 'override':
  108. value = descriptor.value;
  109. break;
  110. case 'delegate':
  111. if (/^invoke|invokeOneway$/.test(descriptor.to)) {
  112. if (is.generatorFunction(proto[name])) {
  113. value = function* (...args) {
  114. return yield cb => { client[symbols.invoke](name, args, cb); };
  115. };
  116. } else if (is.function(proto[name])) {
  117. if (descriptor.to === 'invoke') {
  118. value = (...args) => {
  119. let cb;
  120. if (is.function(args[args.length - 1])) {
  121. cb = args.pop();
  122. }
  123. // whether callback or promise
  124. if (cb) {
  125. client[symbols.invoke](name, args, cb);
  126. } else {
  127. return new Promise((resolve, reject) => {
  128. client[symbols.invoke](name, args, function(err) {
  129. if (err) {
  130. reject(err);
  131. } else {
  132. resolve.apply(null, Array.from(arguments).slice(1));
  133. }
  134. });
  135. });
  136. }
  137. };
  138. } else {
  139. value = (...args) => {
  140. client[symbols.invoke](name, args);
  141. };
  142. }
  143. } else {
  144. throw new Error(`[ClusterClient] api: ${name} not implement in client`);
  145. }
  146. } else {
  147. value = client[Symbol.for(`ClusterClient#${descriptor.to}`)];
  148. }
  149. break;
  150. default:
  151. break;
  152. }
  153. Object.defineProperty(client, name, {
  154. value,
  155. writable: true,
  156. enumerable: true,
  157. configurable: true,
  158. });
  159. }
  160. return client;
  161. }
  162. _generateDescriptors() {
  163. const clientClass = this._clientClass;
  164. const proto = clientClass.prototype;
  165. const needGenerateMethods = new Set(autoGenerateMethods);
  166. for (const entry of this._descriptors.entries()) {
  167. const key = entry[0];
  168. const value = entry[1];
  169. if (needGenerateMethods.has(key) ||
  170. (value.type === 'delegate' && needGenerateMethods.has(value.to))) {
  171. needGenerateMethods.delete(key);
  172. }
  173. }
  174. for (const method of needGenerateMethods.values()) {
  175. if (is.function(proto[method])) {
  176. this.delegate(method, method);
  177. }
  178. }
  179. const keys = Reflect.ownKeys(proto)
  180. .filter(key => typeof key !== 'symbol' &&
  181. !key.startsWith('_') &&
  182. !this._descriptors.has(key));
  183. for (const key of keys) {
  184. const descriptor = Reflect.getOwnPropertyDescriptor(proto, key);
  185. if (descriptor.value &&
  186. (is.generatorFunction(descriptor.value) || is.asyncFunction(descriptor.value))) {
  187. this.delegate(key);
  188. }
  189. }
  190. }
  191. }
  192. module.exports = function(clientClass, options) {
  193. return new ClientGenerator(clientClass, options);
  194. };