@wishcore/wish-rpc 0.6.14 → 0.6.15

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.
@@ -1,43 +1,96 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Server = void 0;
4
+ const method_registry_1 = require("./method-registry");
5
+ const debug = console.log;
2
6
  /**
3
- * The idea is to be able to easily register methods/functions to be called
4
- * remotely, to enable access control and return data directly, asynchronously or
5
- * opening a stream which delivers data for long running tasks or large data
6
- * transfers.
7
+ * RPC Server for registering and handling remote procedure calls.
8
+ *
9
+ * Supports both modern endpoint registration and legacy underscore-prefixed methods.
10
+ * Provides real-time communication with support for streaming, events, and async operations.
11
+ *
12
+ * Type-safe endpoint registration with custom configuration:
13
+ * ```typescript
14
+ * interface ReasonEndpoint extends BaseEndpointConfig {
15
+ * validateArgs?: (args: any[], context: any) => boolean;
16
+ * permissions?: string[];
17
+ * rateLimit?: number;
18
+ * }
7
19
  *
8
- * When registerCore is run with the wish core instance, it reads all objects
9
- * that have a property named "_". It scans those properties for properties
10
- * which have a descriptor property with the same name prepended with "_".
20
+ * const server = new Server<ReasonEndpoint>();
21
+ * server.endpoint('user.get', {
22
+ * doc: 'Get user by ID',
23
+ * args: 'userId: string',
24
+ * validateArgs: (args, context) => validateUserArgs(args),
25
+ * permissions: ['user', 'admin'],
26
+ * rateLimit: 100
27
+ * }, async (req, res) => {
28
+ * const user = await getUserById(req.args[0]);
29
+ * res.send(user);
30
+ * });
31
+ * ```
11
32
  *
12
- * Sub modules are not implemented yet, but an example should look like the
13
- * relay, module below.
33
+ * Default usage (backwards compatible):
34
+ * ```typescript
35
+ * const server = new Server();
36
+ * server.endpoint('user.get', {
37
+ * doc: 'Get user by ID',
38
+ * args: 'userId: string',
39
+ * validate: (args, context) => validateUserArgs(args)
40
+ * }, async (req, res) => {
41
+ * const user = await getUserById(req.args[0]);
42
+ * res.send(user);
43
+ * });
44
+ * ```
14
45
  *
15
- * Ex.
16
- * rpc.insertMethods({
17
- * _doThis: {
18
- * doc: "This function does this, and emits progress each second and end.",
19
- * },
20
- * doThis: function(req,res) {
21
- * ...
22
- * },
23
- * _relay: {
24
- * doc: "A bunch of relay related functions"
25
- * },
26
- * relay: {
27
- * _test: {},
28
- * test: function() {},
29
- * _list: {},
30
- * list: function() {}
31
- * }
32
- * });
46
+ * Legacy method registration (backwards compatibility):
47
+ * ```typescript
48
+ * server.insertMethods({
49
+ * _getUser: {
50
+ * doc: 'Get user by ID',
51
+ * args: 'userId: string'
52
+ * },
53
+ * getUser: async (req, res) => {
54
+ * const user = await getUserById(req.args[0]);
55
+ * res.send(user);
56
+ * }
57
+ * });
58
+ * ```
59
+ *
60
+ * Accessing metadata for custom validation and introspection:
61
+ * ```typescript
62
+ * // Get method metadata and check for custom validation properties
63
+ * const meta = server.getMethodMeta('user.get');
64
+ * if (meta?.validateArgs) {
65
+ * const isValid = meta.validateArgs(args, context);
66
+ * }
67
+ * if (meta?.schema) {
68
+ * const result = meta.schema.safeParse(args); // e.g., Zod
69
+ * }
70
+ * if (meta?.validator) {
71
+ * meta.validator.validate(args); // e.g., Joi
72
+ * }
73
+ *
74
+ * // Generate documentation from all methods
75
+ * const allMeta = server.getAllMethodMeta();
76
+ * for (const [name, config] of Object.entries(allMeta)) {
77
+ * console.log(`${name}: ${config.doc || 'No documentation'}`);
78
+ * if (config.args) console.log(` Args: ${config.args}`);
79
+ * }
80
+ * ```
81
+ *
82
+ * Features:
83
+ * - Type-safe endpoint registration with TypeScript
84
+ * - Streaming support for large data transfers
85
+ * - Real-time events and signals
86
+ * - Request validation and error handling
87
+ * - Session management and cleanup
88
+ * - Method discovery and documentation
89
+ * - Metadata access for external validation and introspection
33
90
  */
34
- Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.Server = void 0;
36
- const debug = console.log;
37
91
  class Server {
38
92
  constructor(methods) {
39
- this.modules = {};
40
- this.methods = {};
93
+ this.registry = new method_registry_1.MethodRegistry();
41
94
  this.requests = {};
42
95
  this.clientIdCounter = 0;
43
96
  /** internal request id counter */
@@ -58,85 +111,87 @@ class Server {
58
111
  delete this.requests[i];
59
112
  }
60
113
  }
61
- insertMethods(o) {
62
- const path = '';
63
- this.addMethods(path, o);
64
- }
65
- addMethods(path, o) {
66
- //console.log("addMethods", path, o);
67
- const prefix = path ? path + '.' : '';
68
- for (const i in o) {
69
- if (i === '_') {
70
- // module meta
71
- }
72
- else if (i.substring(0, 1) !== '_') {
73
- if (o['_' + i]) {
74
- // publish is _funcName exists for funcName
75
- if (typeof o[i] === 'function') {
76
- const ip = o['_' + i].name || i;
77
- o['_' + i].fullname = prefix + ip;
78
- try {
79
- this.modules[prefix + ip] = o['_' + i];
80
- this.methods[prefix + ip] = o[i];
81
- }
82
- catch (e) {
83
- console.log('Kaboom! ', e);
84
- }
85
- }
86
- else if (typeof o[i] === 'object') {
87
- // this is a sub item
88
- //this.modules[prefix + i] = o['_' + i];
89
- this.addMethods(prefix + i, o[i]);
90
- }
91
- }
92
- else {
93
- // no _funcName found, do not publish via RPC
94
- }
95
- }
96
- }
114
+ /**
115
+ * Legacy method for backwards compatibility
116
+ * Registers methods using the old underscore-prefixed structure
117
+ */
118
+ insertMethods(methodMap) {
119
+ this.registry.registerLegacyMethods(methodMap);
97
120
  }
98
- listMethods(args, context, cb) {
99
- const result = {};
100
- function copy(s, filter) {
101
- const d = {};
102
- for (const j in s) {
103
- if (filter[j]) {
104
- continue;
105
- }
106
- d[j] = s[j];
107
- }
108
- return d;
121
+ /**
122
+ * Lists all available methods and their configurations
123
+ */
124
+ listMethods(args, context, callback) {
125
+ try {
126
+ const result = this.registry.getMethodListing();
127
+ callback(null, result);
109
128
  }
110
- const filter = { fullname: true };
111
- //console.log("no acl.");
112
- for (const i in this.modules) {
113
- result[i] = copy(this.modules[i], filter);
129
+ catch (error) {
130
+ callback(error);
114
131
  }
115
- cb(null, result);
116
132
  }
133
+ /**
134
+ * Cleanup when a client goes offline
135
+ */
117
136
  clientOffline(clientId) {
118
- for (const i in this.requests[clientId]) {
119
- if (typeof this.requests[clientId][i].end === 'function') {
120
- this.requests[clientId][i].end();
137
+ if (!this.requests[clientId]) {
138
+ return; // Client not found
139
+ }
140
+ try {
141
+ for (const requestId in this.requests[clientId]) {
142
+ this.cleanupRequest(clientId, requestId);
121
143
  }
122
- //console.log("Ended due to client offline:", this.requests[clientId][i]);
123
- delete this.requests[clientId][i];
144
+ delete this.requests[clientId];
145
+ }
146
+ catch (error) {
147
+ console.error(`Error during client ${clientId} offline cleanup:`, error);
124
148
  }
125
149
  }
150
+ /**
151
+ * Close all active connections and cleanup
152
+ */
126
153
  closeAll() {
127
- for (const i in this.requests) {
128
- for (const j in this.requests[i]) {
129
- if (typeof this.requests[i][j].end === 'function') {
130
- this.requests[i][j].end();
154
+ try {
155
+ for (const clientId in this.requests) {
156
+ for (const requestId in this.requests[clientId]) {
157
+ this.cleanupRequest(parseInt(clientId), requestId);
131
158
  }
132
- //console.log("Ended due to client offline:", this.requests[i][j]);
133
- delete this.requests[i][j];
159
+ delete this.requests[clientId];
160
+ }
161
+ }
162
+ catch (error) {
163
+ console.error('Error during closeAll cleanup:', error);
164
+ }
165
+ }
166
+ /**
167
+ * Safely cleanup a specific request
168
+ */
169
+ cleanupRequest(clientId, requestId) {
170
+ var _a, _b;
171
+ try {
172
+ const request = (_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[requestId];
173
+ if (request && typeof request.end === 'function') {
174
+ request.end();
134
175
  }
176
+ (_b = this.requests[clientId]) === null || _b === void 0 ? true : delete _b[requestId];
177
+ }
178
+ catch (error) {
179
+ console.error(`Error cleaning up request ${requestId} for client ${clientId}:`, error);
135
180
  }
136
181
  }
182
+ /**
183
+ * Parse and handle incoming RPC messages
184
+ */
137
185
  parse(msg, respond, context, clientId) {
138
186
  if (typeof clientId !== 'number') {
139
- //console.log("Got RPC request from unspecified client, setting clientId to __none.");
187
+ console.warn('Got RPC request from unspecified client');
188
+ return;
189
+ }
190
+ if (!msg || typeof msg.op !== 'string') {
191
+ respond({
192
+ err: msg === null || msg === void 0 ? void 0 : msg.id,
193
+ data: { code: 400, msg: 'Invalid message format' }
194
+ });
140
195
  return;
141
196
  }
142
197
  try {
@@ -154,7 +209,6 @@ class Server {
154
209
  }
155
210
  if (msg.end) {
156
211
  const id = msg.end;
157
- //console.log("end this request:", clientId, id, this.requests[clientId] ? this.requests[clientId][id] : this.requests);
158
212
  if (this.requests[clientId] && this.requests[clientId][id]) {
159
213
  if (typeof this.requests[clientId][id].end === 'function') {
160
214
  this.requests[clientId][id].end();
@@ -164,10 +218,10 @@ class Server {
164
218
  return;
165
219
  }
166
220
  if (!this.requests[clientId][id]) {
167
- return; // console.log("No such request...", id, clientId, new Error().stack, this.requests[clientId], this.requests);
221
+ return;
168
222
  }
169
223
  if (typeof this.requests[clientId][id].end === 'function') {
170
- this.requests[id].end();
224
+ this.requests[clientId][id].end();
171
225
  }
172
226
  return;
173
227
  }
@@ -177,8 +231,7 @@ class Server {
177
231
  });
178
232
  return;
179
233
  }
180
- if (typeof this.methods[msg.op] === 'undefined') {
181
- // service not found
234
+ if (!this.registry.hasMethod(msg.op)) {
182
235
  respond({ err: msg.id, data: { code: 300, msg: 'No method found: ' + msg.op } });
183
236
  return;
184
237
  }
@@ -207,140 +260,156 @@ class Server {
207
260
  }
208
261
  }
209
262
  }
263
+ /**
264
+ * Process raw RPC invocation
265
+ */
210
266
  invokeRaw(msg, respond, context, clientId) {
211
- var _a, _b;
212
267
  ++this.requestId;
213
268
  if (!this.requests[clientId]) {
214
269
  return console.log('Session not opened for client. Use Server.open() first');
215
270
  }
216
271
  if (msg.push) {
217
- // we got a sig from client... neat, must be streaming!
218
- const ctx = this.requests[clientId][msg.push];
219
- //console.log('stream context:', ctx);
220
- if (msg.data === null) {
221
- console.log('msg.data is null:', msg);
222
- return;
223
- }
224
- ctx.data(msg.data);
272
+ this.handlePushMessage(msg, clientId);
225
273
  return;
226
274
  }
227
275
  if (msg.sig) {
228
- // we got a sig from client... neat, must be streaming!
229
- const ctx = this.requests[clientId][msg.sig];
230
- // call the actual method
231
- try {
232
- this.methods[ctx.op].call(this.requests[clientId][msg.sig], msg.data, {
233
- send: (data) => {
234
- if (typeof ctx.end === 'function') {
235
- ctx.end();
236
- }
237
- if (this.requests[clientId][msg.sig]) {
238
- delete this.requests[clientId][msg.sig];
239
- }
240
- respond({ ack: msg.sig, data: data });
241
- },
242
- emit: (data) => {
243
- if (!this.requests[clientId][msg.sig]) {
244
- if (typeof ctx.end === 'function') {
245
- ctx.end();
246
- }
247
- }
248
- return respond({ sig: msg.sig, data: data });
249
- },
250
- error: (data) => {
251
- if (typeof ctx.end === 'function') {
252
- ctx.end();
253
- }
254
- delete this.requests[clientId][msg.sig];
255
- respond({ err: msg.sig, data: data });
256
- },
257
- close: (data) => {
258
- if (typeof ctx.end === 'function') {
259
- ctx.end();
260
- }
261
- delete this.requests[clientId][msg.sig];
262
- respond({ fin: msg.sig });
263
- }
264
- }, context);
265
- }
266
- catch (e) {
267
- console.log('Calling the method in RPC failed:', msg.op, msg.args, e.stack);
268
- delete this.requests[clientId][msg.sig];
269
- respond({ err: msg.sig, data: { msg: 'rpc failed during execution of ' + msg.op, code: 578 } });
270
- }
276
+ this.handleSignalMessage(msg, respond, context, clientId);
271
277
  return;
272
278
  }
273
279
  if (typeof msg.id === 'undefined') {
274
- // this is an event
275
- const reqCtx = {};
276
- this.methods[msg.op].call(reqCtx, { args: msg.args }, {
280
+ this.handleEventMessage(msg, respond, context);
281
+ }
282
+ else {
283
+ this.handleRegularRequest(msg, respond, context, clientId);
284
+ }
285
+ }
286
+ /**
287
+ * Handle push (streaming data) messages
288
+ */
289
+ handlePushMessage(msg, clientId) {
290
+ if (!msg.push) {
291
+ return;
292
+ }
293
+ const ctx = this.requests[clientId][msg.push];
294
+ if (!ctx) {
295
+ return;
296
+ }
297
+ if (msg.data === null) {
298
+ console.log('msg.data is null:', msg);
299
+ return;
300
+ }
301
+ ctx.data(msg.data);
302
+ }
303
+ /**
304
+ * Handle signal (streaming) messages
305
+ */
306
+ handleSignalMessage(msg, respond, context, clientId) {
307
+ if (!msg.sig) {
308
+ return;
309
+ }
310
+ const ctx = this.requests[clientId][msg.sig];
311
+ if (!ctx) {
312
+ return;
313
+ }
314
+ try {
315
+ const method = this.registry.getMethod(ctx.op);
316
+ if (method) {
317
+ method.call(this.requests[clientId][msg.sig], msg.data, this.createSignalResponseHandlers(msg.sig, respond, ctx, clientId), context);
318
+ }
319
+ }
320
+ catch (e) {
321
+ console.log('Calling the method in RPC failed:', msg.op, msg.args, e.stack);
322
+ delete this.requests[clientId][msg.sig];
323
+ respond({ err: msg.sig, data: { msg: 'rpc failed during execution of ' + msg.op, code: 578 } });
324
+ }
325
+ }
326
+ /**
327
+ * Create response handlers for signal messages
328
+ */
329
+ createSignalResponseHandlers(sigId, respond, ctx, clientId) {
330
+ return {
331
+ send: (data) => {
332
+ if (typeof ctx.end === 'function') {
333
+ ctx.end();
334
+ }
335
+ if (this.requests[clientId][sigId]) {
336
+ delete this.requests[clientId][sigId];
337
+ }
338
+ respond({ ack: sigId, data: data });
339
+ },
340
+ emit: (data) => {
341
+ if (!this.requests[clientId][sigId]) {
342
+ if (typeof ctx.end === 'function') {
343
+ ctx.end();
344
+ }
345
+ }
346
+ return respond({ sig: sigId, data: data });
347
+ },
348
+ error: (data) => {
349
+ if (typeof ctx.end === 'function') {
350
+ ctx.end();
351
+ }
352
+ delete this.requests[clientId][sigId];
353
+ respond({ err: sigId, data: data });
354
+ },
355
+ close: (data) => {
356
+ if (typeof ctx.end === 'function') {
357
+ ctx.end();
358
+ }
359
+ delete this.requests[clientId][sigId];
360
+ respond({ fin: sigId });
361
+ }
362
+ };
363
+ }
364
+ /**
365
+ * Handle event messages (no response expected)
366
+ */
367
+ handleEventMessage(msg, respond, context) {
368
+ const reqCtx = {};
369
+ const method = this.registry.getMethod(msg.op);
370
+ if (method) {
371
+ method.call(reqCtx, { args: msg.args }, {
277
372
  send: () => { debug('Trying to send response to event (' + msg.op + '). Dropping.'); },
278
373
  emit: () => { debug('Trying to emit response to event (' + msg.op + '). Dropping.'); },
279
374
  error: () => { debug('Trying to respond with error to event (' + msg.op + '). Dropping.'); },
280
375
  close: () => { debug('Trying to close event (' + msg.op + '). Dropping.'); }
281
376
  }, context);
282
377
  }
283
- else {
284
- // this is a regular rpc request
285
- const reqCtx = {
286
- id: msg.id,
287
- op: msg.op,
288
- args: msg.args,
289
- context,
290
- end: null,
291
- send: respond,
292
- };
293
- if (!this.requests[clientId]) {
294
- this.requests[clientId] = {};
378
+ }
379
+ /**
380
+ * Handle regular RPC requests
381
+ */
382
+ handleRegularRequest(msg, respond, context, clientId) {
383
+ var _a, _b;
384
+ const reqCtx = {
385
+ id: msg.id,
386
+ op: msg.op,
387
+ args: msg.args,
388
+ context,
389
+ end: null,
390
+ send: respond,
391
+ };
392
+ if (!this.requests[clientId]) {
393
+ this.requests[clientId] = {};
394
+ }
395
+ // Check for duplicate request ID
396
+ if (msg.id && ((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msg.id])) {
397
+ console.log("Serious warning. There is already a request by that id, we'll kill it off! This session is likely not clean. clientId:", clientId, 'msg', msg, 'stack trace:', new Error().stack);
398
+ if (msg.id && typeof this.requests[clientId][msg.id].end === 'function') {
399
+ this.requests[clientId][msg.id].end();
295
400
  }
296
- if ((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msg.id]) {
297
- console.log("Serious warning. There is already a request by that id, we'll kill it off! This session is likely not clean. clientId:", clientId, 'msg', msg, 'stack trace:', new Error().stack);
298
- if (typeof this.requests[clientId][msg.id].end === 'function') {
299
- this.requests[clientId][msg.id].end();
300
- }
401
+ if (msg.id) {
301
402
  delete this.requests[clientId][msg.id];
302
- console.log('Removed old request by same id from same client...');
303
403
  }
404
+ console.log('Removed old request by same id from same client...');
405
+ }
406
+ if (msg.id) {
304
407
  this.requests[clientId][msg.id] = reqCtx;
305
- // call the actual method
306
408
  try {
307
- this.methods[msg.op].call(reqCtx, { args: msg.args }, {
308
- send: (data) => {
309
- var _a;
310
- if (typeof reqCtx.end === 'function') {
311
- reqCtx.end();
312
- }
313
- if ((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msg.id]) {
314
- delete this.requests[clientId][msg.id];
315
- }
316
- respond({ ack: msg.id, data: data });
317
- },
318
- emit: (data) => {
319
- var _a;
320
- if (!((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msg.id])) {
321
- if (typeof reqCtx.end === 'function') {
322
- reqCtx.end();
323
- }
324
- }
325
- return respond({ sig: msg.id, data: data });
326
- },
327
- error: (data) => {
328
- var _a;
329
- if (typeof reqCtx.end === 'function') {
330
- reqCtx.end();
331
- }
332
- (_a = this.requests[clientId]) === null || _a === void 0 ? true : delete _a[msg.id];
333
- respond({ err: msg.id, data: data });
334
- },
335
- close: (data) => {
336
- var _a;
337
- if (typeof reqCtx.end === 'function') {
338
- reqCtx.end();
339
- }
340
- (_a = this.requests[clientId]) === null || _a === void 0 ? true : delete _a[msg.id];
341
- respond({ fin: msg.id });
342
- }
343
- }, context);
409
+ const method = this.registry.getMethod(msg.op);
410
+ if (method) {
411
+ method.call(reqCtx, { args: msg.args }, this.createRegularResponseHandlers(msg.id, respond, reqCtx, clientId), context);
412
+ }
344
413
  }
345
414
  catch (e) {
346
415
  console.log('Calling the method in RPC failed:', msg.op, msg.args, e.stack);
@@ -349,39 +418,48 @@ class Server {
349
418
  }
350
419
  }
351
420
  }
352
- /*
353
- invoke(op: string, args: any[], stream: any, cb): void {
354
- if(typeof stream === 'function') { cb = stream; stream = null; }
355
-
356
- if( !Array.isArray(args) ) {
357
- args = [args];
358
- }
359
-
360
- if (typeof cb !== 'function') {
361
- throw new Error('RPC invoke requires callback function as third argument');
362
- }
363
-
364
- const msg: RequestMessage = {
365
- op,
366
- args,
367
- stream,
368
- id: ++this.invokeId
369
- };
370
- const context = {
371
- clientType: 'invoke',
372
- clientId: 'invokedViaRPCInvoke'
373
- };
374
-
375
- const response = (reply) => {
376
- const ctx = { cancel: () => { this.parse({ end: msg.id }, () => { }, {}, 0); }, id: msg.id };
377
- process.nextTick(() => {
378
- cb.call(ctx, reply.err ? true : null, reply.data);
379
- });
421
+ /**
422
+ * Create response handlers for regular requests
423
+ */
424
+ createRegularResponseHandlers(msgId, respond, reqCtx, clientId) {
425
+ return {
426
+ send: (data) => {
427
+ var _a;
428
+ if (typeof reqCtx.end === 'function') {
429
+ reqCtx.end();
430
+ }
431
+ if ((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msgId]) {
432
+ delete this.requests[clientId][msgId];
433
+ }
434
+ respond({ ack: msgId, data: data });
435
+ },
436
+ emit: (data) => {
437
+ var _a;
438
+ if (!((_a = this.requests[clientId]) === null || _a === void 0 ? void 0 : _a[msgId])) {
439
+ if (typeof reqCtx.end === 'function') {
440
+ reqCtx.end();
441
+ }
442
+ }
443
+ return respond({ sig: msgId, data: data });
444
+ },
445
+ error: (data) => {
446
+ var _a;
447
+ if (typeof reqCtx.end === 'function') {
448
+ reqCtx.end();
449
+ }
450
+ (_a = this.requests[clientId]) === null || _a === void 0 ? true : delete _a[msgId];
451
+ respond({ err: msgId, data: data });
452
+ },
453
+ close: (data) => {
454
+ var _a;
455
+ if (typeof reqCtx.end === 'function') {
456
+ reqCtx.end();
457
+ }
458
+ (_a = this.requests[clientId]) === null || _a === void 0 ? true : delete _a[msgId];
459
+ respond({ fin: msgId });
460
+ }
380
461
  };
381
-
382
- this.parse(msg, response, context, '__invoke');
383
462
  }
384
- */
385
463
  close(clientId) {
386
464
  for (const i in this.requests[clientId]) {
387
465
  if (typeof this.requests[clientId][i].end === 'function') {
@@ -403,40 +481,22 @@ class Server {
403
481
  * @param handler - The endpoint handler function
404
482
  */
405
483
  endpoint(name, config, handler) {
406
- // Parse the endpoint name into parts
407
- const parts = name.split('.');
408
- const methodName = parts[parts.length - 1];
409
- const namespaceParts = parts.slice(0, -1);
410
- // Build the nested object structure
411
- const methods = {};
412
- let current = methods;
413
- // Navigate/create the namespace structure
414
- for (let i = 0; i < namespaceParts.length; i++) {
415
- const part = namespaceParts[i];
416
- if (!current[part]) {
417
- current[part] = {};
418
- }
419
- current = current[part];
420
- }
421
- // Add the validation object (with _ prefix)
422
- const validationKey = `_${methodName}`;
423
- current[validationKey] = {};
424
- if (config.doc) {
425
- current[validationKey].doc = config.doc;
426
- }
427
- if (config.args) {
428
- current[validationKey].args = config.args;
429
- }
430
- if (config.validate) {
431
- current[validationKey].validate = config.validate;
432
- }
433
- if (config.validateResult) {
434
- current[validationKey].validateResult = config.validateResult;
435
- }
436
- // Add the handler
437
- current[methodName] = handler;
438
- // Register with the RPC server
439
- this.insertMethods(methods);
484
+ this.registry.registerEndpoint(name, config, handler);
485
+ }
486
+ /**
487
+ * Get metadata for a specific method (including all custom properties)
488
+ * @param name - Method name
489
+ * @returns The complete method configuration or undefined if not found
490
+ */
491
+ getMethodMeta(name) {
492
+ return this.registry.getModule(name);
493
+ }
494
+ /**
495
+ * Get metadata for all registered methods
496
+ * @returns Object containing all method configurations
497
+ */
498
+ getAllMethodMeta() {
499
+ return this.registry.getModules();
440
500
  }
441
501
  }
442
502
  exports.Server = Server;