systemlynx 1.19.12 → 1.20.0

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.
package/API.md CHANGED
@@ -1,200 +1,782 @@
1
- **IN WORKING PROGRESS**
2
-
3
1
  # SystemLynx API Documentation
4
2
 
5
- Welcome to the docs! Following is a list of the objects used and created when developing web APIs with SystemLynx. SystemLynx is an end-to-end framework for developing modular, microservices software systems in NodeJS. Check out [**Quick Start**](https://github.com/Odion100/SystemLynx#quick-start) for an example of how simple it is to develope object-orientated APIs with SystemLynx.
3
+ SystemLynx is a Node.js framework for building modular, distributed web APIs. It lets you host plain JavaScript objects as **Modules** on a server and load them transparently into a client application, calling their methods over HTTP or WebSockets.
4
+
5
+ For a high-level introduction see the [README](./README.md).
6
6
 
7
- <details>
8
- <summary><b><a href="https://github.com/Odion100/SystemLynx/blob/tasksjs2.0/API.md#app">App</a></b></summary>
9
-
10
- - [**startService(options)**](https://github.com/Odion100/SystemLynx/blob/tasksjs2.0/API.md#appstartserviceoptions)
11
- - [**loadService(name, url)**](https://github.com/Odion100/SystemLynx/blob/tasksjs2.0/API.md#apploadserviceurl)
12
- - [**onLoad(callback)**](https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md#apponloadcallback)
13
- - [**module(name, constructor [,reserved_methods])**]()
14
- - [**config(constructor)**](https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md#appconfigconstructor)
15
- - [**on(event, callback)**]()
16
- - [**emit(event, payload)**]()
7
+ ---
17
8
 
18
- </details>
9
+ ## Table of Contents
19
10
 
20
- <details>
21
- <summary><b><a href="https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md#client">Client</a></b></summary>
22
-
23
- - [**loadService(url)**]()
11
+ - [Service](#service)
12
+ - [ServerModule](#servermodule)
13
+ - [Middleware](#middleware)
14
+ - [App](#app)
15
+ - [Client](#client)
16
+ - [ClientModule](#clientmodule)
17
+ - [LoadBalancer](#loadbalancer)
18
+ - [HttpClient](#httpclient)
19
+
20
+ ---
24
21
 
25
- </details>
22
+ ## Service
26
23
 
27
- <details>
28
- <summary><b><a href="https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md#service">Service</a></b></summary>
29
-
30
- - [**startService(options)**]()
31
- - [**module(name, constructor [,options])**]()
32
- - [**Server()**]()
33
- - [**WebSocket()**]()
24
+ `Service` is used to host objects that can be loaded remotely by a SystemLynx `Client`.
34
25
 
35
- </details>
26
+ ```javascript
27
+ const { Service } = require("systemlynx");
28
+ ```
36
29
 
37
- <details>
38
- <summary><b><a href="https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md#service">LoadBalancer</a></b></summary>
39
-
40
- - [**startService(options)**]()
41
- - [**module(name, constructor [,options])**]()
42
- - [**Server()**]()
43
- - [**WebSocket()**]()
44
- - [**clones**]()
45
- - [**register(options)**]()
46
- - [**dispatch(event)**]()
47
- - [**assignDispatch(event)**]()
30
+ Alternatively, use `createService` to create an isolated instance:
48
31
 
49
- </details>
32
+ ```javascript
33
+ const { createService } = require("systemlynx");
34
+ const Service = createService();
35
+ ```
50
36
 
51
37
  ---
52
38
 
53
- <details>
54
- <summary><b><a href="https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md">ServerModule</a></b></summary>
55
-
56
- - [**...constructedMethods**]()
57
- - [**on(name, callback)**]()
58
- - [**emit(name, data)**]()
39
+ ### Service.module(name, constructor [, reserved_methods])
40
+
41
+ Registers an object or constructor function as a **ServerModule** hosted by this Service. Returns the constructed module.
42
+
43
+ | Parameter | Type | Description |
44
+ |:---|:---|:---|
45
+ | `name` | string | The name used to identify and route to this module |
46
+ | `constructor` | object \| function | The module object, or a constructor function where `this` is the module instance |
47
+ | `reserved_methods` | string[] | Method names to exclude from routing (not accessible remotely) |
48
+
49
+ **Object constructor:**
50
+ ```javascript
51
+ const Users = {
52
+ add(data) {
53
+ return { message: "User added" };
54
+ },
55
+ find(query) {
56
+ return { message: "Users found" };
57
+ }
58
+ };
59
+
60
+ Service.module("Users", Users);
61
+ ```
62
+
63
+ **Function constructor:**
59
64
 
60
- </details>
65
+ When a function is used, `this` inside it is the module instance. Methods added to `this` are exposed remotely. The constructor receives the Express server and Socket.io instance as arguments, useful for adding custom routes or listeners.
61
66
 
62
- <details>
63
- <summary><b><a href="https://github.com/Odion100/SystemLynx/tasksjs2.0/API.md">ClientModule</a></b></summary>
64
-
65
- - [**...loadedMethods**]()
66
- - [**on(name, callback)**]()
67
- - [**emit(name, data)**]()
67
+ ```javascript
68
+ Service.module("Orders", function (server, WebSocket) {
69
+ const Orders = this;
70
+
71
+ Orders.find = async function (query) {
72
+ return { results: [] };
73
+ };
74
+
75
+ Orders.create = async function (data) {
76
+ return { created: true };
77
+ };
78
+
79
+ // Scope middleware to specific methods
80
+ Orders.before("create", validateData);
81
+ Orders.after("find", formatResults);
82
+ });
83
+ ```
68
84
 
69
- </details>
85
+ **Excluding methods from routing:**
86
+
87
+ ```javascript
88
+ Service.module("Users", Users, ["internalMethod", "helperFn"]);
89
+ ```
70
90
 
71
91
  ---
72
92
 
73
- ## Service
93
+ ### Service.startService(options)
94
+
95
+ Starts the Express and Socket.io servers and sets up routing for all registered modules. Returns a promise that resolves with the service connection data once the server is listening.
96
+
97
+ ```javascript
98
+ await Service.startService({
99
+ route: "api/users",
100
+ port: 4400,
101
+ host: "localhost",
102
+ });
103
+ ```
74
104
 
75
- In SystemLynx a **Service** is a class used to host objects that can be later loaded into a client application using a SystemLynx **Client.** Get a handle on a SystemLynx **Service** by de-structuring the SystemLynx export.
105
+ | Option | Type | Required | Description |
106
+ |:---|:---|:---:|:---|
107
+ | `route` | string | ✓ | The base route for this service (e.g. `"api/users"`) |
108
+ | `port` | number | ✓ | The port to listen on |
109
+ | `host` | string | | Hostname for the service URL. Default: `"localhost"` |
110
+ | `protocol` | string | | `"http"` or `"https"`. Inferred from `ssl` if not set |
111
+ | `ssl` | object | | `{ key, cert }` — file contents for HTTPS. When provided, an HTTPS server is created |
112
+ | `useREST` | boolean | | When `true`, module methods named `get`, `post`, `put`, or `delete` are also exposed as REST routes. Default: `false` |
76
113
 
114
+ **With SSL:**
77
115
  ```javascript
78
- const { Service } = require(“systemlynx”)
116
+ const fs = require("fs");
117
+
118
+ await Service.startService({
119
+ route: "api/users",
120
+ port: 443,
121
+ host: "example.com",
122
+ protocol: "https",
123
+ ssl: {
124
+ key: fs.readFileSync("/path/to/key.pem"),
125
+ cert: fs.readFileSync("/path/to/cert.pem"),
126
+ },
127
+ });
79
128
  ```
80
129
 
81
- ## Service.module(name, constructor)
130
+ ---
131
+
132
+ ### Service.before([name,] ...middleware)
82
133
 
83
- Use the `Service.module(name, constructor/object)` method to create a **ServerModule**, which is an object that is hosted by a **SystemLynx Service**. This will allows you to later load an instance of that object into a client application. The **Service.module(name, constructor)** method takes the (string) name assigned to the object as the first argument, and the object itself, or a constructor function, as the second argument, and will return the constructed **ServerModule.** See the examples below.
134
+ Adds middleware that runs **before** a module method is called.
135
+
136
+ - With no name — runs before every method on every module
137
+ - With a module name — runs before every method on that module
138
+ - With `"ModuleName.methodName"` — runs before that specific method only
139
+ - With `"$all"` — same as no name, explicitly runs before everything
84
140
 
85
141
  ```javascript
86
- const { Service } = require("systemlynx");
142
+ // Before every method on every module
143
+ Service.before(authenticate);
87
144
 
88
- const UsersConstructor = {
89
- add:function(data) {
90
- return { message: "You have successfully called Users.add(...)" };
91
- }
92
- };
93
- constr OrdersConstructor = function () {
94
- const Orders = this;
145
+ // Same, explicit form
146
+ Service.before("$all", authenticate);
147
+
148
+ // Before every method on the Users module
149
+ Service.before("Users", requireAuth);
150
+
151
+ // Before only Users.delete
152
+ Service.before("Users.delete", requireAdmin);
153
+
154
+ // Multiple middleware in one call
155
+ Service.before("Users.edit", validate, sanitize);
156
+ ```
157
+
158
+ ---
159
+
160
+ ### Service.after([name,] ...middleware)
161
+
162
+ Adds middleware that runs **after** a module method returns, before the response is sent. Same scoping rules as `Service.before`.
163
+
164
+ ```javascript
165
+ // After every method
166
+ Service.after(logResponse);
167
+
168
+ // After every method on Orders
169
+ Service.after("Orders", formatResponse);
170
+
171
+ // After only Orders.find
172
+ Service.after("Orders.find", attachMetadata);
173
+ ```
174
+
175
+ ---
176
+
177
+ ### Service.server
178
+
179
+ The underlying Express app instance. Use this to add custom Express routes, static file serving, or any Express middleware.
180
+
181
+ ```javascript
182
+ Service.module("Storage", function (server) {
183
+ const express = require("express");
184
+ server.use("/files", express.static("/path/to/files"));
185
+
186
+ this.list = () => { /* ... */ };
187
+ });
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Service.WebSocket
193
+
194
+ The Socket.io server instance.
195
+
196
+ ---
197
+
198
+ ## ServerModule
199
+
200
+ A `ServerModule` is the object returned by `Service.module()`. It is also the value of `this` inside a module constructor function. It has the following built-in methods in addition to whatever methods you define.
201
+
202
+ ---
203
+
204
+ ### module.on(eventName, callback [, options])
205
+
206
+ Listens for a WebSocket event emitted to this module's namespace. Returns an unsubscribe function.
207
+
208
+ ```javascript
209
+ const Users = Service.module("Users", function () {
210
+ this.on("user_connected", (data) => {
211
+ console.log("User connected:", data);
212
+ });
213
+ });
214
+ ```
215
+
216
+ | Option | Type | Description |
217
+ |:---|:---|:---|
218
+ | `eventId` | string | Stable key for this listener. Re-registering with the same `eventId` replaces the previous listener instead of adding a new one |
219
+ | `interval` | number | Throttle interval in milliseconds |
220
+ | `limit` | number | Max calls within the throttle interval |
95
221
 
96
- Orders.find = function (arg1, arg2) {
97
- return { message: "You have successfully called the Orders.find(...)" };
222
+ ---
223
+
224
+ ### module.once(eventName, callback [, options])
225
+
226
+ Same as `on`, but the listener is automatically removed after firing once. Returns an unsubscribe function.
227
+
228
+ ---
229
+
230
+ ### module.emit(eventName, data)
231
+
232
+ Emits a WebSocket event to all connected clients listening on this module.
233
+
234
+ ```javascript
235
+ Service.module("Orders", function () {
236
+ this.create = function (data) {
237
+ const order = createOrder(data);
238
+ this.emit("order_created", order);
239
+ return order;
98
240
  };
241
+ });
242
+ ```
243
+
244
+ This is also available in middleware via `req.module.emit(...)`:
245
+
246
+ ```javascript
247
+ function notifyClients(req, res, next) {
248
+ req.module.emit(`updated:${req.returnValue._id}`, req.returnValue);
249
+ next();
99
250
  }
251
+ ```
252
+
253
+ ---
254
+
255
+ ### module.$clearEvent(eventName [, fn])
256
+
257
+ Removes event listeners. If a function is provided, removes only the listener matching that function's name. If no function is provided, removes all listeners for that event.
258
+
259
+ ```javascript
260
+ module.$clearEvent("order_created"); // remove all
261
+ module.$clearEvent("order_created", myHandler); // remove by name
262
+ ```
263
+
264
+ ---
265
+
266
+ ### module.destroy()
267
+
268
+ Removes all event listeners on this module.
100
269
 
101
- const Users = Service.module("Users", UsersConstructor);
270
+ ---
271
+
272
+ ### module.before([name,] ...middleware)
273
+
274
+ Adds middleware scoped to this module. Same as `Service.before("ModuleName", ...)` but callable from within the constructor.
102
275
 
103
- const Orders = Service.module("Orders", OrdersConstructor);
276
+ ```javascript
277
+ Service.module("Users", function () {
278
+ this.edit = editUser;
279
+ this.delete = deleteUser;
280
+
281
+ this.before("edit", validateEditData);
282
+ this.before("delete", requireAdmin);
283
+ this.after("$all", logAction);
284
+ });
104
285
  ```
105
286
 
106
- ## Service.startService(options)
287
+ ---
288
+
289
+ ### module.after([name,] ...middleware)
290
+
291
+ Same as `module.before` but runs after the method returns.
292
+
293
+ ---
294
+
295
+ ## Middleware
107
296
 
108
- Use the `Service.startService(options)` method to setup hosting and routing for the **Service**. Calling this method will start an **ExpressJS** Server and a **Socket.io** WebSocket Server, and allow the modules created by the **Service** to be loaded into a client application. This method returns a promise that will resolve once the Express server is running.
297
+ Middleware functions follow the Express convention: `(req, res, next) => {}`. They can be async.
298
+
299
+ SystemLynx adds the following properties to the request and response objects.
300
+
301
+ ### Request properties
302
+
303
+ | Property | Type | Description |
304
+ |:---|:---|:---|
305
+ | `req.module_name` | string | Name of the module being called (e.g. `"Users"`) |
306
+ | `req.fn` | string | Name of the method being called (e.g. `"find"`) |
307
+ | `req.arguments` | array | The arguments passed by the client to the method |
308
+ | `req.module` | object | The ServerModule instance itself |
309
+ | `req.returnValue` | any | The value returned by the method — available in `after` middleware |
310
+
311
+ Any properties you add to `req` are accessible in subsequent middleware in the chain:
109
312
 
110
313
  ```javascript
111
- const { Service } = require("systemlynx");
112
- const route = "my-route/whatever";
113
- const port = 8100;
114
- const host = "localhost";
314
+ function authenticate(req, res, next) {
315
+ const token = req.headers.authorization;
316
+ const session = verifyToken(token);
115
317
 
116
- const promise = Service.startService({ route, port, host });
318
+ if (!session) return res.sendError({ status: 401, message: "Unauthorized" });
319
+
320
+ req.session = session;
321
+ next();
322
+ }
323
+
324
+ function requireAdmin(req, res, next) {
325
+ if (!req.session.isAdmin)
326
+ return res.sendError({ status: 403, message: "Forbidden" });
327
+ next();
328
+ }
329
+ ```
330
+
331
+ ### Response methods
332
+
333
+ | Method | Description |
334
+ |:---|:---|
335
+ | `res.sendError({ status, message })` | Sends an error response and stops the middleware chain |
336
+ | `res.sendResponse(value)` | Sends a successful response with the given value |
337
+
338
+ ```javascript
339
+ function validate(req, res, next) {
340
+ const [data] = req.arguments;
341
+ if (!data.name) return res.sendError({ status: 400, message: "name is required" });
342
+ next();
343
+ }
117
344
  ```
118
345
 
119
- Following is a list of options that can be passed to the **Service.startService(options)** method.
346
+ **Modifying the return value in `after` middleware:**
120
347
 
121
- | Name | Type | O/R/C | Description |
122
- | :------------ | :-----: | :---: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
123
- | route | string | R | The route from which the service can be loaded. |
124
- | port | number | R | The port on which to start the Express server. |
125
- | host | string | O | The host from which the **Service** can be reached. |
126
- | socketPort | string | R | The port on which to start the Socket.io Websocket server. <br/><br/> Default value : **_random for digit number_** |
127
- | useRest | boolean | O | When this is true a RESTful route will be created for any **ServerModule** method which is named after a REST method <br/><br/> Default value: `false` |
128
- | useService | boolean | O | The route from which the service can be loaded. |
129
- | staticRouting | boolean | O | The route from which the service can be loaded. |
130
- | middleware | string | R | The route from which the service can be loaded. |
348
+ ```javascript
349
+ function attachMeta(req, res, next) {
350
+ req.returnValue.timestamp = Date.now();
351
+ next();
352
+ }
353
+ ```
354
+
355
+ ---
131
356
 
132
357
  ## App
133
358
 
134
- **App** combinds the both functionalites of SystemLynx Service and Client into one object, while also providing a module interface and lifecycle events. Access the App instance by deconcatanating from the object return when loading SystemLynx `require("systemlynx")`.
359
+ `App` combines the functionality of `Service` and `Client` into a single object with a chainable interface and lifecycle events. It is the recommended entry point for production services.
135
360
 
136
361
  ```javascript
137
362
  const { App } = require("systemlynx");
138
363
  ```
139
364
 
140
- ## App.module(name, constructor [,reserved_methods])
365
+ To use a custom Express server or create isolated instances:
141
366
 
142
- Use **App.module(name, constructor)** function to create or pass an object that can be loaded by a SystemLynx Client.
367
+ ```javascript
368
+ const { createApp } = require("systemlynx");
369
+ const express = require("express");
143
370
 
144
- - **_name_** (string) - name assigned to the module or object
145
- - **_constructor_** (object/function) -
371
+ const server = express();
372
+ const App = createApp(server);
373
+ ```
146
374
 
147
- ## App.startService(options)
375
+ ---
148
376
 
149
- ## App.loadService(name, url)
377
+ ### App.startService(options)
150
378
 
151
- ## App.onLoad(callback)
379
+ Same options as [`Service.startService`](#servicestartserviceoptions). Returns `App` for chaining.
152
380
 
153
- ## App.module(name, constructor)
381
+ ```javascript
382
+ App.startService({ route: "api/profiles", port: 4400, host: "localhost" });
383
+ ```
154
384
 
155
- ## App.config(constructor)
385
+ ---
386
+
387
+ ### App.module(name, constructor [, reserved_methods])
388
+
389
+ Same as [`Service.module`](#servicemodulename-constructor--reserved_methods). Returns `App` for chaining.
390
+
391
+ ```javascript
392
+ App.startService({ route: "api", port: 4400 })
393
+ .module("Users", UsersConstructor)
394
+ .module("Orders", OrdersConstructor);
395
+ ```
156
396
 
157
397
  ---
158
398
 
159
- ## Client
399
+ ### App.loadService(name, url)
400
+
401
+ Loads a remote SystemLynx Service and makes it available inside module constructors via `this.useService(name)`. Returns `App` for chaining.
160
402
 
161
- ## Client.loadService(url)
403
+ ```javascript
404
+ App.startService({ route: "api/basketball", port: 4401 })
405
+ .loadService("Profiles", "http://localhost:4400/api/profiles")
406
+ .module("Games", function () {
407
+ const Profiles = this.useService("Profiles");
408
+
409
+ this.create = async function (data) {
410
+ const profile = await Profiles.Users.get({ id: data.userId });
411
+ // ...
412
+ };
413
+ });
414
+ ```
162
415
 
163
416
  ---
164
417
 
165
- ## Service
418
+ ### App.onLoad(callback)
166
419
 
167
- Service is a SystemLynx abstraction used to server objects that can be loaded by a SystemLynx Client using the `Client.loadService(url)` method.
420
+ Fires a callback when the most recently chained `loadService` connects. Receives the loaded service object.
168
421
 
169
- Call require("systemlynx") and de-concatenate from the object it returns.
422
+ ```javascript
423
+ App.loadService("Profiles", url)
424
+ .onLoad((Profiles) => {
425
+ console.log("Profiles service connected:", Object.keys(Profiles));
426
+ });
427
+ ```
428
+
429
+ ---
430
+
431
+ ### App.config(constructor)
432
+
433
+ Registers a configuration constructor that runs before modules are initialized. Use it to set up shared state or configuration accessible to all modules via `this.useConfig()`.
170
434
 
171
435
  ```javascript
172
- const { Service } = require("systemlynx");
436
+ App.config(function (next) {
437
+ this.dbConnection = connectToDatabase();
438
+ this.settings = loadSettings();
439
+ next();
440
+ });
441
+
442
+ App.module("Users", function () {
443
+ const config = this.useConfig();
444
+ this.db = config.dbConnection;
445
+ });
173
446
  ```
174
447
 
175
- The Service object has the following methods:
448
+ ---
176
449
 
177
- - **_Service.module(name, constructor [,reserved_methods])_** - Used to create or pass an object that is hosted by the Service.
178
- - **_Service.startService(options)_** - Used
179
- - **_Service.Server()_** - Returns the expressJS app instance used to handle routing to the _Services_.
180
- - **_Service.WebSocket()_** - Returns socket.io WebSocket instance used to emit events from the _Services_.
450
+ ### App.before / App.after
181
451
 
182
- ## Service.module(name, constructor [,reserved_methods])
452
+ Same as [`Service.before`](#servicebeforename-middleware) and [`Service.after`](#serviceafterename-middleware).
183
453
 
184
- - **Name** - String -
185
- Use the `Service.module(name, constructor, [,options])` method to register an object to be hosted by a _SystemLynx Service_. This will allows you to load an instance of that object onto a client application, and call any methods on that object remotely.
454
+ ```javascript
455
+ App.startService({ route: "api", port: 4400 })
456
+ .before("$all", authenticate)
457
+ .module("Users", Users)
458
+ .module("Orders", Orders);
459
+ ```
460
+
461
+ ---
462
+
463
+ ### App.on(eventName, callback)
464
+
465
+ Listens for a lifecycle event on the App. Returns an unsubscribe function.
466
+
467
+ | Event | Payload | Description |
468
+ |:---|:---|:---|
469
+ | `"ready"` | system object | Fires when the App has fully initialized — service started, all modules loaded, all remote services connected |
470
+ | `"service_loaded"` | service object | Fires each time a remote service finishes connecting |
471
+ | `"service_loaded:<name>"` | service object | Fires when the named service finishes connecting |
472
+ | `"failed_connection"` | `{ err, name, url }` | Fires when a remote service fails to connect |
186
473
 
187
474
  ```javascript
188
- const { Service } = require("systemlynx");
475
+ App.on("ready", function (system) {
476
+ console.log("App ready");
477
+ console.log("Modules:", system.modules.map(m => m.name));
478
+ });
479
+
480
+ App.on("service_loaded:Profiles", (Profiles) => {
481
+ console.log("Profiles connected");
482
+ });
483
+ ```
484
+
485
+ The `this` value inside a `"ready"` callback is the system context, giving access to `this.useModule()`, `this.useService()`, and `this.useConfig()`.
189
486
 
190
- const Users = {};
487
+ ---
488
+
489
+ ### App.emit(eventName, data)
490
+
491
+ Emits a lifecycle event on the App.
492
+
493
+ ---
191
494
 
192
- Users.add = function (data, callback) {
193
- console.log(data);
194
- callback(null, { message: "You have successfully called the Users.add method" });
495
+ ### App.use(plugin)
496
+
497
+ Registers a plugin function that runs before App initialization. The plugin receives `(App, system)` and can add modules, load services, or configure the App.
498
+
499
+ ```javascript
500
+ const myPlugin = (App, system) => {
501
+ App.loadService("Analytics", "http://localhost:4500/analytics");
502
+ App.module("Tracker", TrackerConstructor);
195
503
  };
504
+
505
+ App.use(myPlugin).startService({ route: "api", port: 4400 });
506
+ ```
507
+
508
+ ---
509
+
510
+ ### System context inside modules
511
+
512
+ Inside any module constructor (and `App.on("ready", ...)` callbacks), `this` includes:
513
+
514
+ | Method | Description |
515
+ |:---|:---|
516
+ | `this.useModule(name)` | Returns another module registered on this App by name |
517
+ | `this.useService(name)` | Returns a remote service loaded via `App.loadService(name, url)` |
518
+ | `this.useConfig()` | Returns the configuration object built by `App.config(constructor)` |
519
+
520
+ ```javascript
521
+ App.module("Games", function () {
522
+ const Users = this.useModule("Users");
523
+ const Profiles = this.useService("Profiles");
524
+
525
+ this.create = async function (data) {
526
+ const user = await Users.get({ id: data.userId });
527
+ const profile = await Profiles.Players.get({ id: data.userId });
528
+ // ...
529
+ };
530
+ });
531
+ ```
532
+
533
+ ---
534
+
535
+ ## Client
536
+
537
+ `Client` is used to load a remote SystemLynx Service and call its module methods from a client application.
538
+
539
+ ```javascript
540
+ const { Client } = require("systemlynx");
541
+ ```
542
+
543
+ ---
544
+
545
+ ### Client.loadService(url [, options])
546
+
547
+ Loads a remote SystemLynx Service. Returns a promise that resolves into an object containing all the modules hosted by that service, plus service-level methods.
548
+
549
+ ```javascript
550
+ const { Users, Orders } = await Client.loadService("http://localhost:4400/api");
551
+ ```
552
+
553
+ | Option | Type | Description |
554
+ |:---|:---|:---|
555
+ | `forceReload` | boolean | When `true`, bypasses the cache and reconnects even if this URL was already loaded |
556
+
557
+ **Loading multiple services:**
558
+
559
+ ```javascript
560
+ const ProfilesAPI = await Client.loadService("http://localhost:4400/api/profiles");
561
+ const BasketballAPI = await Client.loadService("http://localhost:4401/api/basketball");
562
+
563
+ const user = await ProfilesAPI.Users.get({ id: "abc123" });
564
+ const games = await BasketballAPI.Games.find({ userId: "abc123" });
565
+ ```
566
+
567
+ **Service-level methods on the returned object:**
568
+
569
+ | Method | Description |
570
+ |:---|:---|
571
+ | `service.on(event, callback)` | Listen for service-level WebSocket events (`"connect"`, `"disconnect"`, `"reconnect"`) |
572
+ | `service.setHeaders(headers)` | Set default headers sent with every request from this service |
573
+ | `service.headers()` | Returns the current default headers |
574
+ | `service.resetConnection()` | Manually trigger a reconnection |
575
+ | `service.disconnect()` | Disconnect the WebSocket |
576
+
577
+ ---
578
+
579
+ ## ClientModule
580
+
581
+ Each module on a loaded service is a `ClientModule`. Calling any method returns a promise that resolves with the method's return value.
582
+
583
+ ```javascript
584
+ const { Users } = await Client.loadService(url);
585
+
586
+ const result = await Users.find({ city: "New York" });
587
+ ```
588
+
589
+ File uploads are handled automatically — include a `file` (single) or `files` (array) property on any argument object:
590
+
591
+ ```javascript
592
+ const result = await Storage.save({
593
+ file: fs.createReadStream("/path/to/photo.jpg"),
594
+ userId: "abc123",
595
+ });
596
+
597
+ const result = await Storage.save({
598
+ files: [fs.createReadStream("a.jpg"), fs.createReadStream("b.jpg")],
599
+ albumId: "xyz",
600
+ });
601
+ ```
602
+
603
+ ---
604
+
605
+ ### module.on(eventName, callback [, options])
606
+
607
+ Listens for WebSocket events emitted by the server-side module. Returns an unsubscribe function — useful for React `useEffect` cleanup.
608
+
609
+ ```javascript
610
+ const unsubscribe = Orders.on("order_created", (data) => {
611
+ console.log("New order:", data);
612
+ });
613
+
614
+ // Later, to clean up:
615
+ unsubscribe();
616
+ ```
617
+
618
+ | Option | Type | Description |
619
+ |:---|:---|:---|
620
+ | `eventId` | string | Stable key for this listener. Re-registering with the same `eventId` replaces the previous listener. Useful in React re-renders |
621
+ | `interval` | number | Throttle interval in milliseconds |
622
+ | `limit` | number | Max calls within the throttle interval |
623
+
624
+ **React usage:**
625
+ ```javascript
626
+ useEffect(() => {
627
+ const unsubscribe = Orders.on("order_created", handleNewOrder, {
628
+ eventId: "order-list-listener",
629
+ });
630
+ return unsubscribe;
631
+ }, []);
632
+ ```
633
+
634
+ ---
635
+
636
+ ### module.once(eventName, callback [, options])
637
+
638
+ Same as `on`, but fires only once then removes itself automatically. Returns an unsubscribe function.
639
+
640
+ ---
641
+
642
+ ### module.emit(eventName, data)
643
+
644
+ Emits a WebSocket event to the server-side module's namespace.
645
+
646
+ ---
647
+
648
+ ### module.$clearEvent(eventName [, fn])
649
+
650
+ Removes listeners. Without a function argument, removes all listeners for that event.
651
+
652
+ ---
653
+
654
+ ### module.destroy()
655
+
656
+ Removes all listeners on this module.
657
+
658
+ ---
659
+
660
+ ### module.setHeaders(headers)
661
+
662
+ Sets default HTTP headers sent with every request from this module. Module-level headers take priority over service-level headers.
663
+
664
+ ```javascript
665
+ Users.setHeaders({ Authorization: `Bearer ${token}` });
666
+ ```
667
+
668
+ ---
669
+
670
+ ### module.headers()
671
+
672
+ Returns the current module-level headers object.
673
+
674
+ ---
675
+
676
+ ---
677
+
678
+ ## LoadBalancer
679
+
680
+ `LoadBalancer` distributes requests across multiple clones of a Service. Clients connect to the LoadBalancer URL — the LoadBalancer handles routing transparently.
681
+
682
+ ```javascript
683
+ const { LoadBalancer } = require("systemlynx");
196
684
  ```
197
685
 
198
- Service.startService
686
+ ---
687
+
688
+ ### LoadBalancer.startService(options)
689
+
690
+ Same options as [`Service.startService`](#servicestartserviceoptions). Starts the LoadBalancer as a Service.
691
+
692
+ ```javascript
693
+ await LoadBalancer.startService({ route: "api/users", port: 4400 });
694
+ ```
199
695
 
200
696
  ---
697
+
698
+ ### LoadBalancer.clones
699
+
700
+ The `clones` module manages clone registration and routing. It is itself a `ServerModule`, so clients can call its methods remotely.
701
+
702
+ ---
703
+
704
+ #### clones.register(options, callback)
705
+
706
+ Registers a new clone of a service with the LoadBalancer. Once registered, the LoadBalancer will route requests to it using round-robin.
707
+
708
+ ```javascript
709
+ const { clones } = await Client.loadService("http://localhost:4400/api/users");
710
+
711
+ clones.register({ host: "localhost", port: 4401, route: "/api/users" }, (err, result) => {
712
+ if (err) console.error("Registration failed:", err);
713
+ else console.log("Clone registered:", result);
714
+ });
715
+ ```
716
+
717
+ | Option | Type | Required | Description |
718
+ |:---|:---|:---:|:---|
719
+ | `host` | string | ✓ | Hostname of the clone |
720
+ | `port` | number | ✓ | Port the clone is running on |
721
+ | `route` | string | ✓ | Route of the clone service |
722
+
723
+ ---
724
+
725
+ #### clones.dispatch(event, callback)
726
+
727
+ Dispatches a named event to all registered clones.
728
+
729
+ ---
730
+
731
+ #### clones.assignDispatch(event, callback)
732
+
733
+ Assigns handling of an event to a single clone, preventing duplicate handling across instances.
734
+
735
+ ---
736
+
737
+ ## HttpClient
738
+
739
+ A lightweight HTTP client for making requests to SystemLynx services or any HTTP endpoint.
740
+
741
+ ```javascript
742
+ const { HttpClient } = require("systemlynx");
743
+ ```
744
+
745
+ ---
746
+
747
+ ### HttpClient.request(options)
748
+
749
+ Makes an HTTP request. Returns a promise.
750
+
751
+ ```javascript
752
+ const data = await HttpClient.request({
753
+ url: "http://localhost:4400/api/users/Users/find",
754
+ method: "put",
755
+ body: { __arguments: [{ city: "New York" }] },
756
+ headers: { Authorization: "Bearer token" },
757
+ });
758
+ ```
759
+
760
+ | Option | Type | Description |
761
+ |:---|:---|:---|
762
+ | `url` | string | Request URL |
763
+ | `method` | string | HTTP method (`"get"`, `"post"`, `"put"`, `"delete"`). Default: `"get"` |
764
+ | `body` | object | Request body |
765
+ | `headers` | object | Request headers |
766
+
767
+ ---
768
+
769
+ ### HttpClient.upload(options)
770
+
771
+ Uploads files using multipart form data. Returns a promise.
772
+
773
+ ```javascript
774
+ const result = await HttpClient.upload({
775
+ url: "http://localhost:4400/sf/api/storage/Storage/save",
776
+ method: "put",
777
+ formData: {
778
+ __arguments: [{ message: "profile photo" }],
779
+ file: fs.createReadStream("/path/to/photo.jpg"),
780
+ },
781
+ });
782
+ ```