333
schangxiang@126.com
2025-09-19 18966e02fb573c7e2bb0c6426ed792b38b910940
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
'use strict';
 
const assert = require('assert');
const is = require('is-type-of');
 
class Singleton {
  constructor(options = {}) {
    assert(options.name, '[egg:singleton] Singleton#constructor options.name is required');
    assert(options.app, '[egg:singleton] Singleton#constructor options.app is required');
    assert(options.create, '[egg:singleton] Singleton#constructor options.create is required');
    assert(!options.app[options.name], `${options.name} is already exists in app`);
    this.clients = new Map();
    this.app = options.app;
    this.name = options.name;
    this.create = options.create;
    /* istanbul ignore next */
    this.options = options.app.config[this.name] || {};
  }
 
  init() {
    return is.asyncFunction(this.create) ? this.initAsync() : this.initSync();
  }
 
  initSync() {
    const options = this.options;
    assert(!(options.client && options.clients),
      `egg:singleton ${this.name} can not set options.client and options.clients both`);
 
    // alias app[name] as client, but still support createInstance method
    if (options.client) {
      const client = this.createInstance(options.client);
      this.app[this.name] = client;
      this._extendDynamicMethods(client);
      return;
    }
 
    // multi clent, use app[name].getInstance(id)
    if (options.clients) {
      Object.keys(options.clients).forEach(id => {
        const client = this.createInstance(options.clients[id]);
        this.clients.set(id, client);
      });
      this.app[this.name] = this;
      return;
    }
 
    // no config.clients and config.client
    this.app[this.name] = this;
  }
 
  async initAsync() {
    const options = this.options;
    assert(!(options.client && options.clients),
      `egg:singleton ${this.name} can not set options.client and options.clients both`);
 
    // alias app[name] as client, but still support createInstance method
    if (options.client) {
      const client = await this.createInstanceAsync(options.client);
      this.app[this.name] = client;
      this._extendDynamicMethods(client);
      return;
    }
 
    // multi clent, use app[name].getInstance(id)
    if (options.clients) {
      await Promise.all(Object.keys(options.clients).map(id => {
        return this.createInstanceAsync(options.clients[id])
          .then(client => this.clients.set(id, client));
      }));
      this.app[this.name] = this;
      return;
    }
 
    // no config.clients and config.client
    this.app[this.name] = this;
  }
 
  get(id) {
    return this.clients.get(id);
  }
 
  createInstance(config) {
    // async creator only support createInstanceAsync
    assert(!is.asyncFunction(this.create),
      `egg:singleton ${this.name} only support create asynchronous, please use createInstanceAsync`);
    // options.default will be merge in to options.clients[id]
    config = Object.assign({}, this.options.default, config);
    return this.create(config, this.app);
  }
 
  async createInstanceAsync(config) {
    // options.default will be merge in to options.clients[id]
    config = Object.assign({}, this.options.default, config);
    return await this.create(config, this.app);
  }
 
  _extendDynamicMethods(client) {
    assert(!client.createInstance, 'singleton instance should not have createInstance method');
    assert(!client.createInstanceAsync, 'singleton instance should not have createInstanceAsync method');
 
    try {
      let extendable = client;
      // Object.preventExtensions() or Object.freeze()
      if (!Object.isExtensible(client) || Object.isFrozen(client)) {
        // eslint-disable-next-line no-proto
        extendable = client.__proto__ || client;
      }
      extendable.createInstance = this.createInstance.bind(this);
      extendable.createInstanceAsync = this.createInstanceAsync.bind(this);
    } catch (err) {
      this.app.logger.warn('egg:singleton %s dynamic create is disabled because of client is unextensible', this.name);
    }
  }
}
 
module.exports = Singleton;