systemlynx 1.19.12 → 1.21.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,823 @@
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.
6
4
 
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)**]()
5
+ For a high-level introduction see the [README](./README.md).
17
6
 
18
- </details>
7
+ ---
19
8
 
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)**]()
9
+ ## Table of Contents
24
10
 
25
- </details>
11
+ - [Service](#service)
12
+ - [ServerModule](#servermodule)
13
+ - [Middleware](#middleware)
14
+ - [App](#app)
15
+ - [Client](#client)
16
+ - [ClientModule](#clientmodule)
17
+ - [LoadBalancer](#loadbalancer)
18
+ - [HttpClient](#httpclient)
26
19
 
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()**]()
20
+ ---
34
21
 
35
- </details>
22
+ ## Service
36
23
 
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)**]()
24
+ `Service` is used to host objects that can be loaded remotely by a SystemLynx `Client`.
48
25
 
49
- </details>
26
+ ```javascript
27
+ const { Service } = require("systemlynx");
28
+ ```
29
+
30
+ Alternatively, use `createService` to create an isolated instance:
31
+
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:**
64
+
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.
59
66
 
60
- </details>
67
+ ```javascript
68
+ Service.module("Orders", function (server, WebSocket) {
69
+ const Orders = this;
70
+
71
+ Orders.find = async function (query) {
72
+ return { results: [] };
73
+ };
61
74
 
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)**]()
75
+ Orders.create = async function (data) {
76
+ return { created: true };
77
+ };
68
78
 
69
- </details>
79
+ // Scope middleware to specific methods
80
+ Orders.before("create", validateData);
81
+ Orders.after("find", formatResults);
82
+ });
83
+ ```
84
+
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)
74
94
 
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.
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.
76
96
 
77
97
  ```javascript
78
- const { Service } = require(“systemlynx”)
98
+ await Service.startService({
99
+ route: "api/users",
100
+ port: 4400,
101
+ host: "localhost",
102
+ });
79
103
  ```
80
104
 
81
- ## Service.module(name, constructor)
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` |
82
113
 
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.
114
+ **With SSL:**
115
+ ```javascript
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
+ });
128
+ ```
129
+
130
+ ---
131
+
132
+ ### Service.before([name,] ...middleware)
133
+
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 |
221
+
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.
95
227
 
96
- Orders.find = function (arg1, arg2) {
97
- return { message: "You have successfully called the Orders.find(...)" };
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
+ ```
100
252
 
101
- const Users = Service.module("Users", UsersConstructor);
253
+ ---
102
254
 
103
- const Orders = Service.module("Orders", OrdersConstructor);
255
+ ### Built-in `"error"` event
256
+
257
+ SystemLynx emits a local `"error"` event on a module whenever one of its methods sends an error back to the client (a thrown error, a rejected promise, or a middleware error). This is a **server-side, local-only** event — it is *not* broadcast over WebSockets to clients (the failing client already receives the error over HTTP). It lets observers monitor failures that bypass `after` middleware.
258
+
259
+ | Event | Payload | Description |
260
+ |:---|:---|:---|
261
+ | `"error"` | `{ module_name, fn, arguments, status, message, error }` | Fires when a method on this module returns an error to the client |
262
+
263
+ ```javascript
264
+ Service.module("Orders", function () {
265
+ this.on("error", (info) => {
266
+ console.log(`${info.module_name}.${info.fn} failed (${info.status}): ${info.message}`);
267
+ console.log("called with:", info.arguments);
268
+ });
269
+ });
104
270
  ```
105
271
 
106
- ## Service.startService(options)
272
+ A plugin can subscribe across every module via [`App.getModules()`](#appgetmodulename--appgetmodules).
273
+
274
+ ---
275
+
276
+ ### module.$clearEvent(eventName [, fn])
107
277
 
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.
278
+ 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.
109
279
 
110
280
  ```javascript
111
- const { Service } = require("systemlynx");
112
- const route = "my-route/whatever";
113
- const port = 8100;
114
- const host = "localhost";
281
+ module.$clearEvent("order_created"); // remove all
282
+ module.$clearEvent("order_created", myHandler); // remove by name
283
+ ```
284
+
285
+ ---
286
+
287
+ ### module.destroy()
288
+
289
+ Removes all event listeners on this module.
290
+
291
+ ---
292
+
293
+ ### module.before([name,] ...middleware)
115
294
 
116
- const promise = Service.startService({ route, port, host });
295
+ Adds middleware scoped to this module. Same as `Service.before("ModuleName", ...)` but callable from within the constructor.
296
+
297
+ ```javascript
298
+ Service.module("Users", function () {
299
+ this.edit = editUser;
300
+ this.delete = deleteUser;
301
+
302
+ this.before("edit", validateEditData);
303
+ this.before("delete", requireAdmin);
304
+ this.after("$all", logAction);
305
+ });
117
306
  ```
118
307
 
119
- Following is a list of options that can be passed to the **Service.startService(options)** method.
308
+ ---
309
+
310
+ ### module.after([name,] ...middleware)
311
+
312
+ Same as `module.before` but runs after the method returns.
313
+
314
+ ---
315
+
316
+ ## Middleware
317
+
318
+ Middleware functions follow the Express convention: `(req, res, next) => {}`. They can be async.
319
+
320
+ SystemLynx adds the following properties to the request and response objects.
321
+
322
+ ### Request properties
323
+
324
+ | Property | Type | Description |
325
+ |:---|:---|:---|
326
+ | `req.module_name` | string | Name of the module being called (e.g. `"Users"`) |
327
+ | `req.fn` | string | Name of the method being called (e.g. `"find"`) |
328
+ | `req.arguments` | array | The arguments passed by the client to the method |
329
+ | `req.module` | object | The ServerModule instance itself |
330
+ | `req.returnValue` | any | The value returned by the method — available in `after` middleware |
331
+
332
+ Any properties you add to `req` are accessible in subsequent middleware in the chain:
333
+
334
+ ```javascript
335
+ function authenticate(req, res, next) {
336
+ const token = req.headers.authorization;
337
+ const session = verifyToken(token);
120
338
 
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. |
339
+ if (!session) return res.sendError({ status: 401, message: "Unauthorized" });
340
+
341
+ req.session = session;
342
+ next();
343
+ }
344
+
345
+ function requireAdmin(req, res, next) {
346
+ if (!req.session.isAdmin)
347
+ return res.sendError({ status: 403, message: "Forbidden" });
348
+ next();
349
+ }
350
+ ```
351
+
352
+ ### Response methods
353
+
354
+ | Method | Description |
355
+ |:---|:---|
356
+ | `res.sendError({ status, message })` | Sends an error response and stops the middleware chain |
357
+ | `res.sendResponse(value)` | Sends a successful response with the given value |
358
+
359
+ ```javascript
360
+ function validate(req, res, next) {
361
+ const [data] = req.arguments;
362
+ if (!data.name) return res.sendError({ status: 400, message: "name is required" });
363
+ next();
364
+ }
365
+ ```
366
+
367
+ **Modifying the return value in `after` middleware:**
368
+
369
+ ```javascript
370
+ function attachMeta(req, res, next) {
371
+ req.returnValue.timestamp = Date.now();
372
+ next();
373
+ }
374
+ ```
375
+
376
+ ---
131
377
 
132
378
  ## App
133
379
 
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")`.
380
+ `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
381
 
136
382
  ```javascript
137
383
  const { App } = require("systemlynx");
138
384
  ```
139
385
 
140
- ## App.module(name, constructor [,reserved_methods])
386
+ To use a custom Express server or create isolated instances:
141
387
 
142
- Use **App.module(name, constructor)** function to create or pass an object that can be loaded by a SystemLynx Client.
388
+ ```javascript
389
+ const { createApp } = require("systemlynx");
390
+ const express = require("express");
143
391
 
144
- - **_name_** (string) - name assigned to the module or object
145
- - **_constructor_** (object/function) -
392
+ const server = express();
393
+ const App = createApp(server);
394
+ ```
146
395
 
147
- ## App.startService(options)
396
+ ---
148
397
 
149
- ## App.loadService(name, url)
398
+ ### App.startService(options)
150
399
 
151
- ## App.onLoad(callback)
400
+ Same options as [`Service.startService`](#servicestartserviceoptions). Returns `App` for chaining.
152
401
 
153
- ## App.module(name, constructor)
402
+ ```javascript
403
+ App.startService({ route: "api/profiles", port: 4400, host: "localhost" });
404
+ ```
405
+
406
+ ---
407
+
408
+ ### App.module(name, constructor [, reserved_methods])
154
409
 
155
- ## App.config(constructor)
410
+ Same as [`Service.module`](#servicemodulename-constructor--reserved_methods). Returns `App` for chaining.
411
+
412
+ ```javascript
413
+ App.startService({ route: "api", port: 4400 })
414
+ .module("Users", UsersConstructor)
415
+ .module("Orders", OrdersConstructor);
416
+ ```
156
417
 
157
418
  ---
158
419
 
159
- ## Client
420
+ ### App.loadService(name, url)
421
+
422
+ Loads a remote SystemLynx Service and makes it available inside module constructors via `this.useService(name)`. Returns `App` for chaining.
160
423
 
161
- ## Client.loadService(url)
424
+ ```javascript
425
+ App.startService({ route: "api/basketball", port: 4401 })
426
+ .loadService("Profiles", "http://localhost:4400/api/profiles")
427
+ .module("Games", function () {
428
+ const Profiles = this.useService("Profiles");
429
+
430
+ this.create = async function (data) {
431
+ const profile = await Profiles.Users.get({ id: data.userId });
432
+ // ...
433
+ };
434
+ });
435
+ ```
162
436
 
163
437
  ---
164
438
 
165
- ## Service
439
+ ### App.onLoad(callback)
440
+
441
+ Fires a callback when the most recently chained `loadService` connects. Receives the loaded service object.
442
+
443
+ ```javascript
444
+ App.loadService("Profiles", url)
445
+ .onLoad((Profiles) => {
446
+ console.log("Profiles service connected:", Object.keys(Profiles));
447
+ });
448
+ ```
166
449
 
167
- Service is a SystemLynx abstraction used to server objects that can be loaded by a SystemLynx Client using the `Client.loadService(url)` method.
450
+ ---
168
451
 
169
- Call require("systemlynx") and de-concatenate from the object it returns.
452
+ ### App.config(constructor)
453
+
454
+ 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
455
 
171
456
  ```javascript
172
- const { Service } = require("systemlynx");
457
+ App.config(function (next) {
458
+ this.dbConnection = connectToDatabase();
459
+ this.settings = loadSettings();
460
+ next();
461
+ });
462
+
463
+ App.module("Users", function () {
464
+ const config = this.useConfig();
465
+ this.db = config.dbConnection;
466
+ });
173
467
  ```
174
468
 
175
- The Service object has the following methods:
469
+ ---
176
470
 
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_.
471
+ ### App.getModule(name) / App.getModules()
181
472
 
182
- ## Service.module(name, constructor [,reserved_methods])
473
+ Return the live module objects hosted on this App. `getModule(name)` returns a single module (or `undefined`); `getModules()` returns an object keyed by module name (`{ [name]: module }`).
183
474
 
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.
475
+ Modules are constructed during initialization, so the live references only exist **after** the `"ready"` event. Before that, `getModule` returns `undefined` and `getModules` returns an empty object. Call them inside an `App.on("ready", ...)` handler — this is how a plugin (e.g. a monitoring/observability plugin) gets a handle on modules to observe or decorate them.
186
476
 
187
477
  ```javascript
188
- const { Service } = require("systemlynx");
478
+ App.use((App) => {
479
+ App.on("ready", () => {
480
+ Object.entries(App.getModules()).forEach(([name, module]) => {
481
+ module.on("error", (info) => {
482
+ console.log(`[${name}] ${info.module_name}.${info.fn} failed:`, info.message);
483
+ });
484
+ });
485
+ });
486
+ });
487
+ ```
488
+
489
+ ---
490
+
491
+ ### App.before / App.after
492
+
493
+ Same as [`Service.before`](#servicebeforename-middleware) and [`Service.after`](#serviceafterename-middleware).
494
+
495
+ ```javascript
496
+ App.startService({ route: "api", port: 4400 })
497
+ .before("$all", authenticate)
498
+ .module("Users", Users)
499
+ .module("Orders", Orders);
500
+ ```
501
+
502
+ ---
503
+
504
+ ### App.on(eventName, callback)
505
+
506
+ Listens for a lifecycle event on the App. Returns an unsubscribe function.
507
+
508
+ | Event | Payload | Description |
509
+ |:---|:---|:---|
510
+ | `"ready"` | system object | Fires when the App has fully initialized — service started, all modules loaded, all remote services connected |
511
+ | `"service_loaded"` | service object | Fires each time a remote service finishes connecting |
512
+ | `"service_loaded:<name>"` | service object | Fires when the named service finishes connecting |
513
+ | `"failed_connection"` | `{ err, name, url }` | Fires when a remote service fails to connect |
514
+
515
+ ```javascript
516
+ App.on("ready", function (system) {
517
+ console.log("App ready");
518
+ console.log("Modules:", system.modules.map(m => m.name));
519
+ });
520
+
521
+ App.on("service_loaded:Profiles", (Profiles) => {
522
+ console.log("Profiles connected");
523
+ });
524
+ ```
525
+
526
+ The `this` value inside a `"ready"` callback is the system context, giving access to `this.useModule()`, `this.useService()`, and `this.useConfig()`.
527
+
528
+ ---
189
529
 
190
- const Users = {};
530
+ ### App.emit(eventName, data)
191
531
 
192
- Users.add = function (data, callback) {
193
- console.log(data);
194
- callback(null, { message: "You have successfully called the Users.add method" });
532
+ Emits a lifecycle event on the App.
533
+
534
+ ---
535
+
536
+ ### App.use(plugin)
537
+
538
+ Registers a plugin function that runs before App initialization. The plugin receives `(App, system)` and can add modules, load services, or configure the App.
539
+
540
+ ```javascript
541
+ const myPlugin = (App, system) => {
542
+ App.loadService("Analytics", "http://localhost:4500/analytics");
543
+ App.module("Tracker", TrackerConstructor);
195
544
  };
545
+
546
+ App.use(myPlugin).startService({ route: "api", port: 4400 });
547
+ ```
548
+
549
+ ---
550
+
551
+ ### System context inside modules
552
+
553
+ Inside any module constructor (and `App.on("ready", ...)` callbacks), `this` includes:
554
+
555
+ | Method | Description |
556
+ |:---|:---|
557
+ | `this.useModule(name)` | Returns another module registered on this App by name |
558
+ | `this.useService(name)` | Returns a remote service loaded via `App.loadService(name, url)` |
559
+ | `this.useConfig()` | Returns the configuration object built by `App.config(constructor)` |
560
+
561
+ ```javascript
562
+ App.module("Games", function () {
563
+ const Users = this.useModule("Users");
564
+ const Profiles = this.useService("Profiles");
565
+
566
+ this.create = async function (data) {
567
+ const user = await Users.get({ id: data.userId });
568
+ const profile = await Profiles.Players.get({ id: data.userId });
569
+ // ...
570
+ };
571
+ });
572
+ ```
573
+
574
+ ---
575
+
576
+ ## Client
577
+
578
+ `Client` is used to load a remote SystemLynx Service and call its module methods from a client application.
579
+
580
+ ```javascript
581
+ const { Client } = require("systemlynx");
582
+ ```
583
+
584
+ ---
585
+
586
+ ### Client.loadService(url [, options])
587
+
588
+ 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.
589
+
590
+ ```javascript
591
+ const { Users, Orders } = await Client.loadService("http://localhost:4400/api");
592
+ ```
593
+
594
+ | Option | Type | Description |
595
+ |:---|:---|:---|
596
+ | `forceReload` | boolean | When `true`, bypasses the cache and reconnects even if this URL was already loaded |
597
+
598
+ **Loading multiple services:**
599
+
600
+ ```javascript
601
+ const ProfilesAPI = await Client.loadService("http://localhost:4400/api/profiles");
602
+ const BasketballAPI = await Client.loadService("http://localhost:4401/api/basketball");
603
+
604
+ const user = await ProfilesAPI.Users.get({ id: "abc123" });
605
+ const games = await BasketballAPI.Games.find({ userId: "abc123" });
606
+ ```
607
+
608
+ **Service-level methods on the returned object:**
609
+
610
+ | Method | Description |
611
+ |:---|:---|
612
+ | `service.on(event, callback)` | Listen for service-level WebSocket events (`"connect"`, `"disconnect"`, `"reconnect"`) |
613
+ | `service.setHeaders(headers)` | Set default headers sent with every request from this service |
614
+ | `service.headers()` | Returns the current default headers |
615
+ | `service.resetConnection()` | Manually trigger a reconnection |
616
+ | `service.disconnect()` | Disconnect the WebSocket |
617
+
618
+ ---
619
+
620
+ ## ClientModule
621
+
622
+ Each module on a loaded service is a `ClientModule`. Calling any method returns a promise that resolves with the method's return value.
623
+
624
+ ```javascript
625
+ const { Users } = await Client.loadService(url);
626
+
627
+ const result = await Users.find({ city: "New York" });
196
628
  ```
197
629
 
198
- Service.startService
630
+ File uploads are handled automatically — include a `file` (single) or `files` (array) property on any argument object:
631
+
632
+ ```javascript
633
+ const result = await Storage.save({
634
+ file: fs.createReadStream("/path/to/photo.jpg"),
635
+ userId: "abc123",
636
+ });
637
+
638
+ const result = await Storage.save({
639
+ files: [fs.createReadStream("a.jpg"), fs.createReadStream("b.jpg")],
640
+ albumId: "xyz",
641
+ });
642
+ ```
199
643
 
200
644
  ---
645
+
646
+ ### module.on(eventName, callback [, options])
647
+
648
+ Listens for WebSocket events emitted by the server-side module. Returns an unsubscribe function — useful for React `useEffect` cleanup.
649
+
650
+ ```javascript
651
+ const unsubscribe = Orders.on("order_created", (data) => {
652
+ console.log("New order:", data);
653
+ });
654
+
655
+ // Later, to clean up:
656
+ unsubscribe();
657
+ ```
658
+
659
+ | Option | Type | Description |
660
+ |:---|:---|:---|
661
+ | `eventId` | string | Stable key for this listener. Re-registering with the same `eventId` replaces the previous listener. Useful in React re-renders |
662
+ | `interval` | number | Throttle interval in milliseconds |
663
+ | `limit` | number | Max calls within the throttle interval |
664
+
665
+ **React usage:**
666
+ ```javascript
667
+ useEffect(() => {
668
+ const unsubscribe = Orders.on("order_created", handleNewOrder, {
669
+ eventId: "order-list-listener",
670
+ });
671
+ return unsubscribe;
672
+ }, []);
673
+ ```
674
+
675
+ ---
676
+
677
+ ### module.once(eventName, callback [, options])
678
+
679
+ Same as `on`, but fires only once then removes itself automatically. Returns an unsubscribe function.
680
+
681
+ ---
682
+
683
+ ### module.emit(eventName, data)
684
+
685
+ Emits a WebSocket event to the server-side module's namespace.
686
+
687
+ ---
688
+
689
+ ### module.$clearEvent(eventName [, fn])
690
+
691
+ Removes listeners. Without a function argument, removes all listeners for that event.
692
+
693
+ ---
694
+
695
+ ### module.destroy()
696
+
697
+ Removes all listeners on this module.
698
+
699
+ ---
700
+
701
+ ### module.setHeaders(headers)
702
+
703
+ Sets default HTTP headers sent with every request from this module. Module-level headers take priority over service-level headers.
704
+
705
+ ```javascript
706
+ Users.setHeaders({ Authorization: `Bearer ${token}` });
707
+ ```
708
+
709
+ ---
710
+
711
+ ### module.headers()
712
+
713
+ Returns the current module-level headers object.
714
+
715
+ ---
716
+
717
+ ---
718
+
719
+ ## LoadBalancer
720
+
721
+ `LoadBalancer` distributes requests across multiple clones of a Service. Clients connect to the LoadBalancer URL — the LoadBalancer handles routing transparently.
722
+
723
+ ```javascript
724
+ const { LoadBalancer } = require("systemlynx");
725
+ ```
726
+
727
+ ---
728
+
729
+ ### LoadBalancer.startService(options)
730
+
731
+ Same options as [`Service.startService`](#servicestartserviceoptions). Starts the LoadBalancer as a Service.
732
+
733
+ ```javascript
734
+ await LoadBalancer.startService({ route: "api/users", port: 4400 });
735
+ ```
736
+
737
+ ---
738
+
739
+ ### LoadBalancer.clones
740
+
741
+ The `clones` module manages clone registration and routing. It is itself a `ServerModule`, so clients can call its methods remotely.
742
+
743
+ ---
744
+
745
+ #### clones.register(options, callback)
746
+
747
+ Registers a new clone of a service with the LoadBalancer. Once registered, the LoadBalancer will route requests to it using round-robin.
748
+
749
+ ```javascript
750
+ const { clones } = await Client.loadService("http://localhost:4400/api/users");
751
+
752
+ clones.register({ host: "localhost", port: 4401, route: "/api/users" }, (err, result) => {
753
+ if (err) console.error("Registration failed:", err);
754
+ else console.log("Clone registered:", result);
755
+ });
756
+ ```
757
+
758
+ | Option | Type | Required | Description |
759
+ |:---|:---|:---:|:---|
760
+ | `host` | string | ✓ | Hostname of the clone |
761
+ | `port` | number | ✓ | Port the clone is running on |
762
+ | `route` | string | ✓ | Route of the clone service |
763
+
764
+ ---
765
+
766
+ #### clones.dispatch(event, callback)
767
+
768
+ Dispatches a named event to all registered clones.
769
+
770
+ ---
771
+
772
+ #### clones.assignDispatch(event, callback)
773
+
774
+ Assigns handling of an event to a single clone, preventing duplicate handling across instances.
775
+
776
+ ---
777
+
778
+ ## HttpClient
779
+
780
+ A lightweight HTTP client for making requests to SystemLynx services or any HTTP endpoint.
781
+
782
+ ```javascript
783
+ const { HttpClient } = require("systemlynx");
784
+ ```
785
+
786
+ ---
787
+
788
+ ### HttpClient.request(options)
789
+
790
+ Makes an HTTP request. Returns a promise.
791
+
792
+ ```javascript
793
+ const data = await HttpClient.request({
794
+ url: "http://localhost:4400/api/users/Users/find",
795
+ method: "put",
796
+ body: { __arguments: [{ city: "New York" }] },
797
+ headers: { Authorization: "Bearer token" },
798
+ });
799
+ ```
800
+
801
+ | Option | Type | Description |
802
+ |:---|:---|:---|
803
+ | `url` | string | Request URL |
804
+ | `method` | string | HTTP method (`"get"`, `"post"`, `"put"`, `"delete"`). Default: `"get"` |
805
+ | `body` | object | Request body |
806
+ | `headers` | object | Request headers |
807
+
808
+ ---
809
+
810
+ ### HttpClient.upload(options)
811
+
812
+ Uploads files using multipart form data. Returns a promise.
813
+
814
+ ```javascript
815
+ const result = await HttpClient.upload({
816
+ url: "http://localhost:4400/sf/api/storage/Storage/save",
817
+ method: "put",
818
+ formData: {
819
+ __arguments: [{ message: "profile photo" }],
820
+ file: fs.createReadStream("/path/to/photo.jpg"),
821
+ },
822
+ });
823
+ ```