1 | # API Reference
|
2 |
|
3 | ## ModuleProxy
|
4 |
|
5 | ```typescript
|
6 | interface ModuleProxy<T> {}
|
7 | ```
|
8 |
|
9 | Once Alar is imported to the project, this interface will be presented under
|
10 | the global scope, and can be used everywhere.
|
11 |
|
12 | The interface has the following properties and methods (deprecated items are not
|
13 | listed):
|
14 |
|
15 | - `name: string` The name (with namespace) of the module.
|
16 | - `path: string` The path (without extension) of the module.
|
17 | - `exports: any` The very `exports` object of the module.
|
18 | - `proto: T` The very prototype of the module.
|
19 | - `ctor: typeof T extends Function ? T : new (...args: any[]) => T` The very
|
20 | class constructor of the module.
|
21 | - `create(...args: any[]): T` Creates a new instance of the module.
|
22 | - `instance(route?: any): T` Gets the local singleton or a remote instance of
|
23 | the module, if connected to one or more remote instances, the module proxy
|
24 | will automatically calculate the `route` and direct the traffic to the
|
25 | corresponding remote instance. If the given route matches the server ID of
|
26 | any remote service, the corresponding instance will be returned instead.
|
27 | - `fallbackToLocal(enable: boolean): this` If the module is registered as a
|
28 | remote service, however, none of the RPC channel is available, allow calls
|
29 | to fallback to the local instance, which is the default behavior, this
|
30 | method is used to disable (pass `false`) and re-enable (pass `true`) this
|
31 | behavior.
|
32 |
|
33 | **NOTE: RPC calling will serialize all input and output data, those data that**
|
34 | **cannot be serialized will be lost during transmission.**
|
35 |
|
36 | **NOTE: properties cannot be accessed remotely, if trying so, `null` or**
|
37 | **`undefined` will be returned instead, so it's better to declare properties**
|
38 | **`protected` or `private` in any service class that may potentially served**
|
39 | **remotely.**
|
40 |
|
41 | **CHANGE: Since v5.0, every method called from `instance()` is wrapped**
|
42 | **asynchronous, regardless of local call or remote call.**
|
43 |
|
44 | **CHANGE: Since v5.0, a module class with parameters must use the signature**
|
45 | **`ModuleProxy<typeof T>` in order to provide correct type hint for**
|
46 | **`create()` function.**
|
47 |
|
48 | **CHANGE: Since v5.4, the module now can be called as a function, and**
|
49 | **it acts just the same as calling `instance()` function.**
|
50 |
|
51 | **CHANGE: Since v5.5, the module now can be called as a class constructor,**
|
52 | **and it acts just the same as calling `create()` function.**
|
53 |
|
54 | **CHANGE: Since v6.0 `noLocal()` method has be deprecated in favor of**
|
55 | **`fallbackToLocal(false)`.**
|
56 |
|
57 | **CHANGE: v6.0 fix the forced asynchronous behavior added in v5.0, now only**
|
58 | **remote calls and the calls fell back to local are forced asynchronous.**
|
59 |
|
60 | # ModuleProxy (class)
|
61 |
|
62 | ```typescript
|
63 | class ModuleProxy {
|
64 | constructor(name: string, path: string, loader?: ModuleLoader);
|
65 | }
|
66 | ```
|
67 |
|
68 | This class must be imported in order to create a root module proxy, and the root
|
69 | module should be declared as a namespace under the global scope, in TypeScript,
|
70 | the following steps must be walked through for Alar to work in a project.
|
71 |
|
72 | ```typescript
|
73 | import { ModuleProxy } from "alar";
|
74 |
|
75 | // This statement creates a root module and assign it to the global scope in
|
76 | // NodeJS.
|
77 | export const App = global["app"] = new ModuleProxy("app", __dirname);
|
78 |
|
79 | // This declaration merging creates a namespace app under the global scope in
|
80 | // TypeScript, so you can use it everywhere for type hint and type check.
|
81 | declare global {
|
82 | namespace app { }
|
83 | }
|
84 | ```
|
85 |
|
86 | This class has the following extra properties and methods:
|
87 |
|
88 | - `local: symbol` If passed to the `ModuleProxy<T>.instance()`, the method will
|
89 | always return the local instance.
|
90 | - `serve(config: string | RpcOptions, immediate?: boolean): Promise<RpcServer>`
|
91 | Serves an RPC service according to the given configuration. `immediate` sets
|
92 | whether to open the channel immediately after creating the server, it's set
|
93 | `true` by default. However, if you want to do some preparations and register
|
94 | modules before serving, set it to `false`, and call `RpcServer.open()`
|
95 | manually.
|
96 | - `connect(config: string | ClientOptions, immediate?: boolean): Promise<RpcClient>`
|
97 | Connects an RPC service according to the given configuration. `immediate`
|
98 | sets whether to open the channel immediately after creating the client, it's
|
99 | set `true` by default. However, if you want to do some preparations and
|
100 | register modules before connecting, set it to `false`, and call
|
101 | `RpcClient.open()` manually.
|
102 | - `resolve(path: string): string` Resolves the given path to a module name.
|
103 | - `watch(listener?: (event: "change" | "unlink", filename: string)): FSWatcher`
|
104 | Watches file change and reload the corresponding module.
|
105 | - `listener` if provided, it will be called after the module cache has been
|
106 | cleared.
|
107 | - `FSWatcher` is a type exposed by
|
108 | [chokidar](https://github.com/paulmillr/chokidar).
|
109 | - `setLoader(loader: ModuleLoader): void` Sets a custom loader to resolve the
|
110 | module.
|
111 |
|
112 | **CHANGE: Since v5.4, class `ModuleProxy` now takes a third optional parameter**
|
113 | **to set the loader when instantiating.**
|
114 |
|
115 | **CHANGE: Since v6.0, `ModuleProxy.serve()` and `ModuleProxy.connect()` now**
|
116 | **take a second argument to suggest whether the channel should open immediately.**
|
117 |
|
118 | ## ModuleLoader
|
119 |
|
120 | ```typescript
|
121 | export interface ModuleLoader {
|
122 | extension: string | string[],
|
123 | load(filename: string): any;
|
124 | unload(filename: string): void;
|
125 | }
|
126 | ```
|
127 |
|
128 | By default, Alar supports JavaScript modules and (TypeScript modules in
|
129 | **ts-node**), By setting a custom loader, a ModuleProxy instance can resolve any
|
130 | kind of module wanted. (NOTE: The loader must provide cache support.)
|
131 |
|
132 | - `extension` Extension name of the module file, by default, it's `.js` (or `.ts`
|
133 | in ts-node).
|
134 | - `load(filename: string): any` Loads module from the given file or cache.
|
135 | - `unload(filename: string): void` Unloads the module in the cache if the file
|
136 | is modified.
|
137 |
|
138 | ```typescript
|
139 | // Add a loader to resolve JSON modules.
|
140 | const json = new alar.ModuleProxy("json", __dirname + "/json");
|
141 |
|
142 | json.setLoader({
|
143 | cache: {},
|
144 | extension: ".json",
|
145 | load(filename) {
|
146 | return this.cache[filename] || (
|
147 | this.cache[filename] = JSON.parse(fs.readFileSync(filename, "utf8"))
|
148 | );
|
149 | },
|
150 | unload(filename) {
|
151 | delete this.cache[filename];
|
152 | }
|
153 | });
|
154 | ```
|
155 |
|
156 | ## createModuleProxy
|
157 |
|
158 | ```ts
|
159 | export function createModuleProxy(
|
160 | name: string,
|
161 | path: string,
|
162 | loader?: ModuleLoader,
|
163 | singletons?: { [name: string]: any },
|
164 | root?: ModuleProxy // class ModuleProxy, not the interface
|
165 | ): ModuleProxy
|
166 | ```
|
167 |
|
168 | Creates a module proxy manually. This function is used under the hood of Alar
|
169 | framework, however, if you want to create a module proxy whose file path is
|
170 | outside the root proxy, you can use this function to do so.
|
171 |
|
172 | ## RpcOptions
|
173 |
|
174 | ```typescript
|
175 | interface RpcOptions {
|
176 | [x: string]: any,
|
177 | host?: string;
|
178 | port?: number;
|
179 | path?: string;
|
180 | secret?: string;
|
181 | id?: string;
|
182 | codec?: "CLONE" | "JSON" | "BSON" | "FRON"
|
183 | }
|
184 | ```
|
185 |
|
186 | If `path` is provided (equivalent to `ModuleProxy.serve(config: string)` and
|
187 | `ModuleProxy.connect(config: string)`), the RPC channel will be bound to an IPC
|
188 | channel. Otherwise, the RPC channel will be bound to a network channel according
|
189 | to the `host` and `port`.
|
190 |
|
191 | `secret` is used as a password for authentication, if used, the client must
|
192 | provide it as well in order to grant permission to connect.
|
193 |
|
194 | The `id` property is a little ambiguous. On the server-side, if omitted, it will
|
195 | fall back to `dsn`, used for the client routing requests. On the client-side, if
|
196 | omitted, a random string will be generated, used for the server publishing
|
197 | topics.
|
198 |
|
199 | The `codec` property sets in what format should the data be transferred. Since
|
200 | v5.2, Alar uses a new codec `CLONE` by default, it's based on `JSON` however
|
201 | with a structured clone of the original data, that means it supports more types
|
202 | than JSON do, like Date, RegExp, TypedArray, etc. For more information, see
|
203 | [@hyurl/structured-clone](https://github.com/hyurl/structured-clone).
|
204 |
|
205 | If set `BSON` or `FRON`, the following corresponding packages must be installed.
|
206 |
|
207 | - BSON: [bson](https://github.com/mongodb/js-bson)
|
208 | or [bson-ext](https://github.com/mongodb-js/bson-ext);
|
209 | - FRON: [fron](https://github.com/hyurl/fron)
|
210 |
|
211 | ## RpcChannel
|
212 |
|
213 | ```typescript
|
214 | abstract class RpcChannel implements RpcOptions { }
|
215 | ```
|
216 |
|
217 | This abstract class just indicates the RPC channel that allows modules to
|
218 | communicate remotely. methods `ModuleProxy.serve()` and `ModuleProxy.connect()`
|
219 | return its server and client implementations accordingly.
|
220 |
|
221 | The following properties and methods work in both implementations:
|
222 |
|
223 | - `id: string` The unique ID of the server or the client.
|
224 | - `dsn: string` Gets the data source name according to the configuration.
|
225 | - `open(): Promise<this>` Opens the channel. This method will be called
|
226 | automatically by `ModuleProxy.serve()` and `ModuleProxy.connect()` if their
|
227 | `immediate` argument is set `true`.
|
228 | - `close(): Promise<this>` Closes the channel.
|
229 | - `register<T>(mod: ModuleProxy<T>): this` Registers a module to the channel.
|
230 | - `onError(handler: (err: Error) => void)` Binds an error handler invoked
|
231 | whenever an error occurred in asynchronous operations which can't be caught
|
232 | during run-time.
|
233 |
|
234 | ## RpcServer
|
235 |
|
236 | ```typescript
|
237 | class RpcServer extends RpcChannel { }
|
238 | ```
|
239 |
|
240 | The server implementation of the RPC channel.
|
241 |
|
242 | - `publish(topic: string, data: any, clients?: string[]): boolean` Publishes
|
243 | data to the corresponding topic, if `clients` are provided, the topic will
|
244 | only be published to them.
|
245 | - `getClients(): string[]` Returns all IDs of clients that connected to the
|
246 | server.
|
247 |
|
248 | **CHANGE: Prior to v6.0, RpcServer.init() is used to perform initiation for**
|
249 | **registered modules, now the initiation process has been merged to**
|
250 | **`RpcServer.open()`.**
|
251 |
|
252 | ## ClientOptions
|
253 |
|
254 | ```typescript
|
255 | interface ClientOptions extends RpcOptions {
|
256 | timeout?: number;
|
257 | pingInterval?: number;
|
258 | serverId?: string;
|
259 | }
|
260 | ```
|
261 |
|
262 | By default `timeout` is set `5000`ms, it is used to force a timeout error when
|
263 | an RPC request fires and doesn't get a response after a long time.
|
264 |
|
265 | The client uses `pingInterval` to set a timer of ping function so that to
|
266 | ensure the connection is alive. If the server doesn't response when pinging, the
|
267 | client will consider the server is down and will destroy and retry the
|
268 | connection.
|
269 |
|
270 | By default, the `serverId` is automatically set according to the `dsn` of the
|
271 | server, and updated after finishing the connect. However, if an ID is set when
|
272 | serving the RPC server, it would be better to set `serverId` to that ID as well.
|
273 |
|
274 | ### About Reconnection
|
275 |
|
276 | When the client detects the server is down or malfunction, it will destroy the
|
277 | connection positively and retry connect. Since v5.4, this feature uses an
|
278 | exponential back-off mechanism to retry connect rapidly until about 30 minutes
|
279 | timeout before considering the server is down permanently, and close the
|
280 | channel after that.
|
281 |
|
282 | **NOTE: prior to v5.4, reconnection tries timeout is 2 minutes.**
|
283 |
|
284 | ## RpcClient
|
285 |
|
286 | ```typescript
|
287 | class RpcClient extends RpcChannel implements ClientOptions { }
|
288 | ```
|
289 |
|
290 | The client implementation of the RPC channel.
|
291 |
|
292 | - `connecting: boolean` Whether the channel is in connecting state.
|
293 | - `connected: boolean` Whether the channel is connected.
|
294 | - `closed: boolean` Whether the channel is closed.
|
295 | - `pause(): boolean` Pauses the channel and redirect traffic to other channels.
|
296 | - `resume(): boolean` Resumes the channel and continue handling traffic.
|
297 | - `subscribe(topic: string, handle: Subscriber): this` Subscribes a handle
|
298 | function to the corresponding topic.
|
299 | - `unsubscribe(topic: string, handle?: Subscriber): boolean` Unsubscribes the
|
300 | handle function or all handlers from the corresponding topic.
|
301 |
|
302 | The `Subscriber` is a type of
|
303 |
|
304 | ```typescript
|
305 | type Subscriber = (data: any) => void | Promise<void>;
|
306 | ```
|
307 |
|
308 | ## Pub-Sub Model between the server and clients
|
309 |
|
310 | When the server publishes a message, all clients subscribe to the topic
|
311 | will receive the data and invoke their handlers, this mechanism is often used
|
312 | for the server to broadcast data to its clients.
|