UNPKG

4.94 kBPlain TextView Raw
1import hash = require("string-hash");
2import objectHash = require("object-hash");
3import { sep, normalize, dirname, basename, extname } from "path";
4import { applyMagic } from "js-magic";
5import { createLocalInstance, local, remotized, noLocal } from './util';
6import { ModuleLoader } from './index';
7import { deprecate } from "util";
8import { Injectable } from "./di";
9import { readdirSync } from 'fs';
10import cloneDeep = require("lodash/cloneDeep");
11import merge = require("lodash/merge");
12
13const cmd = process.execArgv.concat(process.argv).join(" ");
14const isTsNode = cmd.includes("ts-node");
15const defaultLoader: ModuleLoader = {
16 extension: isTsNode ? ".ts" : ".js",
17 load: require,
18 unload(filename) {
19 delete require.cache[filename];
20 }
21}
22
23@applyMagic
24export class ModuleProxyBase<T = any> extends Injectable implements ModuleProxy<T> {
25 readonly path: string;
26 protected loader: ModuleLoader = defaultLoader;
27 protected singletons: { [name: string]: T } = {};
28 protected remoteSingletons: { [dsn: string]: T } = {};
29 protected children: { [name: string]: ModuleProxy<any> } = {};
30
31 constructor(readonly name: string, path: string) {
32 super();
33 this.path = normalize(path);
34 }
35
36 get exports(): any {
37 if (typeof this.loader.extension === "string") {
38 return this.loader.load(this.path + this.loader.extension);
39 } else {
40 let dir = dirname(this.path);
41 let name = basename(this.path);
42 let files = readdirSync(dir);
43
44 for (let file of files) {
45 let ext = extname(file);
46 let _name = basename(file, ext);
47
48 if (_name === name && this.loader.extension.includes(ext)) {
49 return this.loader.load(this.path + ext);
50 }
51 }
52
53 throw new Error(`Cannot find module '${this.path}'`);
54 }
55 }
56
57 get proto(): T {
58 let { exports } = this;
59
60 if (typeof exports.default === "object")
61 return exports.default;
62 else if (typeof exports.default === "function")
63 return exports.default.prototype;
64 else if (typeof exports === "object")
65 return exports;
66 else if (typeof exports === "function")
67 return exports.prototype;
68 else
69 return null;
70 }
71
72 get ctor(): ModuleConstructor<T> {
73 let { exports } = this;
74
75 if (typeof exports.default === "function")
76 return exports.default;
77 else if (typeof exports === "function")
78 return exports;
79 else
80 return null;
81 }
82
83 create(...args: any[]): T {
84 if (this.ctor) {
85 return new this.ctor(...args);
86 } else if (this.proto) {
87 // return Object.create(<any>this.proto);
88 return merge(cloneDeep(this.proto), args[0]);
89 } else {
90 throw new TypeError(`${this.name} is not a valid module`);
91 }
92 }
93
94 instance(route: any = ""): T {
95 // If the route matches the any key of the remoteSingletons, return the
96 // corresponding singleton as wanted.
97 if (typeof route === "string" && this.remoteSingletons[route]) {
98 return this.remoteSingletons[route];
99 }
100
101 let keys = Object.keys(this.remoteSingletons);
102
103 if (route === local || !this[remotized] || (!keys.length && !this[noLocal])) {
104 return this.singletons[this.name] || (
105 this.singletons[this.name] = createLocalInstance(<any>this)
106 );
107 } else if (keys.length) {
108 // If the module is connected to one or more remote instances,
109 // redirect traffic to one of them automatically.
110 let id = keys[hash(objectHash(route)) % keys.length];
111 return this.remoteSingletons[id];
112 } else {
113 throw new ReferenceError(`Service ${this.name} is not available`);
114 }
115 }
116
117 remote(route: any = ""): T {
118 return this.instance(route);
119 }
120
121 noLocal(): this {
122 this[noLocal] = true;
123 return this;
124 }
125
126 protected __get(prop: string) {
127 if (prop in this) {
128 return this[prop];
129 } else if (prop in this.children) {
130 return this.children[prop];
131 } else if (typeof prop != "symbol") {
132 let child = new ModuleProxyBase(
133 this.name + "." + String(prop),
134 this.path + sep + String(prop)
135 );
136
137 child.singletons = this.singletons;
138 child.loader = this.loader;
139
140 return this.children[prop] = child;
141 }
142 }
143
144 protected __has(prop: string) {
145 return (prop in this) || (prop in this.children);
146 }
147}
148
149ModuleProxyBase.prototype.remote = deprecate(
150 ModuleProxyBase.prototype.remote,
151 "ModuleProxy<T>.route() has been deprecated, use ModuleProxy<T>.instance() instead"
152);
\No newline at end of file