@elysiumjs/core 0.3.2 → 0.4.1

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/src/wamp.ts CHANGED
@@ -12,7 +12,9 @@
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
14
 
15
- import type { Class } from 'type-fest';
15
+ import type { Class, Primitive } from 'type-fest';
16
+
17
+ import { setInterval } from 'node:timers/promises';
16
18
 
17
19
  import { Elysia } from 'elysia';
18
20
  import { omit } from 'radash';
@@ -66,6 +68,18 @@ export type WampSubscriptionOptions = Omit<WampRegistrationOptions, 'invoke'>;
66
68
  * @author Axel Nana <axel.nana@workbud.com>
67
69
  */
68
70
  export type WampProps = {
71
+ /**
72
+ * The name of the connection to use. This connection must be registered in the application properties.
73
+ * @default 'default'
74
+ */
75
+ connection?: string;
76
+ };
77
+
78
+ /**
79
+ * Properties required when declaring a WAMP client in the application properties.
80
+ * @author Axel Nana <axel.nana@workbud.com>
81
+ */
82
+ export type WampClientProps = {
69
83
  /**
70
84
  * The URL to the WAMP server.
71
85
  */
@@ -90,22 +104,157 @@ export type WampProps = {
90
104
  * The interval to wait between retries when connecting to the WAMP server.
91
105
  */
92
106
  retryInterval?: number;
107
+
108
+ /**
109
+ * The authentication ID to use in challenges.
110
+ */
111
+ authid?: string;
112
+
113
+ /**
114
+ * An array of supported authentication methods.
115
+ */
116
+ authmethods?: string[];
117
+
118
+ /**
119
+ * Callback for handling WAMP authentication challenges.
120
+ * @param method The authentication method.
121
+ * @param extra Authentication metadata.
122
+ */
123
+ onChallenge?(method: string, extra: any): string | null;
124
+
125
+ /**
126
+ * Callback for handling the WAMP connection open event.
127
+ */
128
+ onOpen?(): void;
129
+
130
+ /**
131
+ * Callback for handling the WAMP connection close event.
132
+ */
133
+ onClose?(): void;
134
+
135
+ /**
136
+ * Callback for handling the WAMP connection error event.
137
+ */
138
+ onError?(): void;
139
+
140
+ /**
141
+ * Callback for handling the WAMP connection reconnect event.
142
+ */
143
+ onReconnect?(): void;
144
+
145
+ /**
146
+ * Callback for handling the WAMP connection reconnect success event.
147
+ */
148
+ onReconnectSuccess?(): void;
149
+ };
150
+
151
+ export type WampClient = {
152
+ /**
153
+ * Disconnects from WAMP server. Clears all queues, subscriptions and calls.
154
+ * @author Axel Nana <axel.nana@workbud.com>
155
+ */
156
+ disconnect(): Promise<void>;
157
+
158
+ subscribe(
159
+ topic: string,
160
+ handler: WampSubscriptionHandler,
161
+ options?: WampSubscriptionOptions
162
+ ): Promise<{
163
+ topic: string;
164
+ requestId: string;
165
+ subscriptionId: string;
166
+ subscriptionKey: string;
167
+ }>;
168
+
169
+ unsubscribe(
170
+ subscriptionIdOrKey: string,
171
+ handler?: WampSubscriptionHandler
172
+ ): Promise<{
173
+ topic: string;
174
+ requestId: string;
175
+ }>;
176
+
177
+ publish(
178
+ topic: string,
179
+ params?:
180
+ | Primitive
181
+ | Array<any>
182
+ | Record<string, Primitive | Array<any>>
183
+ | { argsList: Array<any>; argsDict: Record<string, any> },
184
+ options?: {
185
+ exclude?: number | number[];
186
+ exclude_authid?: string | string[];
187
+ exclude_authrole?: string | string[];
188
+ eligible?: number | number[];
189
+ eligible_authid?: string | string[];
190
+ eligible_authrole?: string | string[];
191
+ exclude_me?: boolean;
192
+ disclose_me?: boolean;
193
+ ppt_scheme?: string;
194
+ ppt_serializer?: string;
195
+ ppt_cipher?: string;
196
+ ppt_keyid?: string;
197
+ }
198
+ ): Promise<{
199
+ topic: string;
200
+ requestId: string;
201
+ publicationId: string;
202
+ }>;
203
+
204
+ call(
205
+ procedure: string,
206
+ params?:
207
+ | Primitive
208
+ | Array<any>
209
+ | Record<string, Primitive | Array<any>>
210
+ | { argsList: Array<any>; argsDict: Record<string, any> },
211
+ options?: {
212
+ disclose_me?: boolean;
213
+ progress_callback?(args: { argsList: Array<any>; argsDict: Record<string, any> }): void;
214
+ timeout?: number;
215
+ ppt_scheme?: string;
216
+ ppt_serializer?: string;
217
+ ppt_cipher?: string;
218
+ ppt_keyid?: string;
219
+ }
220
+ ): Promise<{
221
+ details: Record<string, any>;
222
+ argsList?: Array<any>;
223
+ argsDict?: Record<string, any>;
224
+ }>;
225
+
226
+ cancel(requestId: string, options?: { mode?: 'kill' | 'killnowait' | 'skip' }): true;
227
+
228
+ register(
229
+ topic: string,
230
+ handler: WampRegistrationHandler,
231
+ options?: WampRegistrationOptions
232
+ ): Promise<{
233
+ topic: string;
234
+ requestId: string;
235
+ registrationId: string;
236
+ }>;
237
+
238
+ unregister(registrationId: string): Promise<{
239
+ topic: string;
240
+ requestId: string;
241
+ }>;
93
242
  };
94
243
 
95
244
  /**
96
245
  * Data sent to a WAMP RPC.
97
246
  * @author Axel Nana <axel.nana@workbud.com>
98
247
  */
99
- export type WampRegistrationHandlerArgs = {
248
+ export type WampRegistrationHandlerArgs<TList = any[], TDict = any> = {
100
249
  /**
101
250
  * The arguments passed to the RPC or subscription.
102
251
  */
103
- argsList: any[];
252
+ argsList: TList;
104
253
 
105
254
  /**
106
255
  * The keyword arguments passed to the RPC or subscription.
107
256
  */
108
- argsDict: any;
257
+ argsDict: TDict;
109
258
 
110
259
  /**
111
260
  * The WAMP details object.
@@ -194,6 +343,127 @@ type WampSubscription = {
194
343
  };
195
344
 
196
345
  export namespace Wamp {
346
+ /**
347
+ * Creates a service name for a WAMP connection.
348
+ * @author Axel Nana <axel.nana@workbud.com>
349
+ * @param name The name of the connection.
350
+ * @returns A service name for the connection.
351
+ */
352
+ const getConnectionName = (name: string) => {
353
+ return `wamp.connection.${name}`;
354
+ };
355
+
356
+ /**
357
+ * Retrieves the client for a WAMP connection.
358
+ * @author Axel Nana <axel.nana@workbud.com>
359
+ * @param name The name of the connection.
360
+ * @returns The Wampy client for the connection with the given name.
361
+ */
362
+ export const getConnection = (name: string) => {
363
+ if (!Service.exists(getConnectionName(name))) {
364
+ // TODO: Use logger service here
365
+ console.error(
366
+ `No WAMP connection with name ${name} found. Please make sure to register the connection before using it.`
367
+ );
368
+ process.exit(1);
369
+ }
370
+
371
+ return Service.get<WampClient>(getConnectionName(name))!;
372
+ };
373
+
374
+ /**
375
+ * Creates and registers a new WAMP client.
376
+ *
377
+ * This will make the registered connection available for dependency injection with
378
+ * the key `wamp.connection.{name}`, where `{name}` is replaced with the given name.
379
+ *
380
+ * @author Axel Nana <axel.nana@workbud.com>
381
+ *
382
+ * @param name The name of the connection.
383
+ * @param config The configuration for the WAMP client.
384
+ * @returns The newly created and configured WAMP client for the given connection.
385
+ */
386
+ export const registerConnection = (name: string, config: WampClientProps) => {
387
+ if (Service.exists(getConnectionName(name))) {
388
+ // TODO: Use logger service here
389
+ console.error(
390
+ `A WAMP connection with name ${name} already exists. Please ensure to register a new connection before overwriting it.`
391
+ );
392
+ process.exit(1);
393
+ }
394
+
395
+ const client = new Wampy(config.url, {
396
+ ws: WampWebsocket,
397
+ ...omit(config, ['url']),
398
+ onError() {
399
+ config.onError?.();
400
+ // TODO: Add more details like controller name and url
401
+ Event.emit('elysium:error', new Error(`An error occurred in WAMP connection ${name}`));
402
+ }
403
+ });
404
+
405
+ const maxRetries = config.maxRetries ?? 5;
406
+ const retryInterval = config.retryInterval ?? 5000;
407
+ let retryCount = 0;
408
+
409
+ const tryConnect = async () => {
410
+ try {
411
+ await client.connect();
412
+ } catch (e) {
413
+ console.error(
414
+ `Failed to connect to WAMP server (attempt ${retryCount + 1}/${maxRetries}):`,
415
+ e
416
+ );
417
+
418
+ if (retryCount < maxRetries) {
419
+ retryCount++;
420
+ setTimeout(tryConnect, retryInterval);
421
+ } else {
422
+ console.error(`Max retry attempts (${maxRetries}) reached. Giving up.`);
423
+ Event.emit(
424
+ 'elysium:error',
425
+ new Error(`Failed to establish WAMP connection ${name} after ${maxRetries} attempts`)
426
+ );
427
+ }
428
+ }
429
+ };
430
+
431
+ tryConnect();
432
+
433
+ return Service.instance<WampClient>(getConnectionName(name), client);
434
+ };
435
+
436
+ /**
437
+ * Checks if a WAMP connection with the given name exists.
438
+ * @author Axel Nana <axel.nana@workbud.com>
439
+ * @param name The name of the connection.
440
+ * @returns `true` if the connection exists, `false` otherwise.
441
+ */
442
+ export const connectionExists = (name: string) => {
443
+ return Service.exists(getConnectionName(name));
444
+ };
445
+
446
+ /**
447
+ * Retrieves the default WAMP connection.
448
+ * @author Axel Nana <axel.nana@workbud.com>
449
+ * @returns The default WAMP connection.
450
+ */
451
+ export const getDefaultConnection = () => {
452
+ return getConnection('default');
453
+ };
454
+
455
+ /**
456
+ * Sets the default WAMP connection.
457
+ * @author Axel Nana <axel.nana@workbud.com>
458
+ * @param name The name of the connection to set as default.
459
+ * @returns The default WAMP connection.
460
+ */
461
+ export const setDefaultConnection = (name: string) => {
462
+ const serviceName = getConnectionName('default');
463
+ Service.remove(serviceName);
464
+ return Service.instance<WampClient>(serviceName, getConnection(name));
465
+ };
466
+
197
467
  /**
198
468
  * Marks a class as a WAMP controller.
199
469
  * @author Axel Nana <axel.nana@workbud.com>
@@ -203,66 +473,50 @@ export namespace Wamp {
203
473
  return function (target: Class<any>) {
204
474
  async function handleWamp(): Promise<Elysia> {
205
475
  // TODO: Use the logger service here
206
- console.log(`Registering Wamp route for ${options.url} using ${target.name}`);
476
+ console.log(
477
+ `Registering Wamp controller for connection ${options.connection} using ${target.name}`
478
+ );
207
479
  await nextTick();
208
480
 
209
481
  const controller = Service.make(target);
210
482
 
211
483
  const metadata = Reflect.getMetadata(Symbols.wamp, target) ?? {};
212
484
 
213
- const w = new Wampy(options.url, {
214
- ws: WampWebsocket,
215
- ...omit(options, ['url']),
216
- onClose: metadata.close?.bind(controller),
217
- onError() {
218
- metadata.error?.call(controller);
219
- // TODO: Add more details like controller name and url
220
- Event.emit('elysium:error', new Error('Wamp connection error'));
221
- },
222
- onReconnect: metadata.reconnect?.bind(controller),
223
- onReconnectSuccess: metadata.reconnectSuccess?.bind(controller)
224
- });
485
+ const w = Wamp.getConnection(options.connection ?? 'default');
225
486
 
226
487
  const app = new Elysia();
227
488
 
228
489
  app.onStart(async (_) => {
229
- try {
230
- await w.connect();
231
- metadata.open?.call(controller);
232
-
233
- const registrations: WampRegistration[] = metadata.registrations ?? [];
234
- const subscriptions: WampSubscription[] = metadata.subscriptions ?? [];
235
-
236
- for (const registration of registrations) {
237
- w.register(
238
- registration.topic,
239
- registration.handler.bind(controller),
240
- registration.options
241
- ).then((r: { topic: string; requestId: string; registrationId: string }) => {
490
+ const registrations: WampRegistration[] = metadata.registrations ?? [];
491
+ const subscriptions: WampSubscription[] = metadata.subscriptions ?? [];
492
+
493
+ for (const registration of registrations) {
494
+ w.register(
495
+ registration.topic,
496
+ registration.handler.bind(controller),
497
+ registration.options
498
+ ).then((r: { topic: string; requestId: string; registrationId: string }) => {
499
+ // TODO: Use the logger service here
500
+ console.log(`Registered ${registration.topic} with id ${r.registrationId}`);
501
+ });
502
+ }
503
+
504
+ for (const subscription of subscriptions) {
505
+ w.subscribe(
506
+ subscription.topic,
507
+ subscription.handler.bind(controller),
508
+ subscription.options
509
+ ).then(
510
+ (s: {
511
+ topic: string;
512
+ requestId: string;
513
+ subscriptionId: string;
514
+ subscriptionKey: string;
515
+ }) => {
242
516
  // TODO: Use the logger service here
243
- console.log(`Registered ${registration.topic} with id ${r.registrationId}`);
244
- });
245
- }
246
-
247
- for (const subscription of subscriptions) {
248
- w.subscribe(
249
- subscription.topic,
250
- subscription.handler.bind(controller),
251
- subscription.options
252
- ).then(
253
- (s: {
254
- topic: string;
255
- requestId: string;
256
- subscriptionId: string;
257
- subscriptionKey: string;
258
- }) => {
259
- // TODO: Use the logger service here
260
- console.log(`Subscribed ${subscription.topic} with id ${s.subscriptionId}`);
261
- }
262
- );
263
- }
264
- } catch (error) {
265
- // noop
517
+ console.log(`Subscribed ${subscription.topic} with id ${s.subscriptionId}`);
518
+ }
519
+ );
266
520
  }
267
521
  });
268
522
 
@@ -314,79 +568,4 @@ export namespace Wamp {
314
568
  Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
315
569
  };
316
570
  };
317
-
318
- /**
319
- * Marks a method as the WAMP "open" event handler.
320
- * @author Axel Nana <axel.nana@workbud.com>
321
- *
322
- * This decorator should be used on a WAMP controller method. Only one "open" event handler
323
- * can be defined per WAMP controller.
324
- */
325
- export const onOpen = (): MethodDecorator => {
326
- return function (target, _propertyKey, descriptor) {
327
- const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
328
- metadata.open = descriptor.value;
329
- Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
330
- };
331
- };
332
-
333
- /**
334
- * Marks a method as the WAMP "close" event handler.
335
- * @author Axel Nana <axel.nana@workbud.com>
336
- *
337
- * This decorator should be used on a WAMP controller method. Only one "close" event handler
338
- * can be defined per WAMP controller.
339
- */
340
- export const onClose = (): MethodDecorator => {
341
- return function (target, _propertyKey, descriptor) {
342
- const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
343
- metadata.close = descriptor.value;
344
- Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
345
- };
346
- };
347
-
348
- /**
349
- * Marks a method as the WAMP "error" event handler.
350
- * @author Axel Nana <axel.nana@workbud.com>
351
- *
352
- * This decorator should be used on a WAMP controller method. Only one "error" event handler
353
- * can be defined per WAMP controller.
354
- */
355
- export const onError = (): MethodDecorator => {
356
- return function (target, _propertyKey, descriptor) {
357
- const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
358
- metadata.error = descriptor.value;
359
- Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
360
- };
361
- };
362
-
363
- /**
364
- * Marks a method as the WAMP "reconnect" event handler.
365
- * @author Axel Nana <axel.nana@workbud.com>
366
- *
367
- * This decorator should be used on a WAMP controller method. Only one "reconnect" event handler
368
- * can be defined per WAMP controller.
369
- */
370
- export const onReconnect = (): MethodDecorator => {
371
- return function (target, _propertyKey, descriptor) {
372
- const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
373
- metadata.reconnect = descriptor.value;
374
- Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
375
- };
376
- };
377
-
378
- /**
379
- * Marks a method as the WAMP "reconnectSuccess" event handler.
380
- * @author Axel Nana <axel.nana@workbud.com>
381
- *
382
- * This decorator should be used on a WAMP controller method. Only one "reconnectSuccess" event handler
383
- * can be defined per WAMP controller.
384
- */
385
- export const onReconnectSuccess = (): MethodDecorator => {
386
- return function (target, _propertyKey, descriptor) {
387
- const metadata = Reflect.getMetadata(Symbols.wamp, target.constructor) ?? {};
388
- metadata.reconnectSuccess = descriptor.value;
389
- Reflect.defineMetadata(Symbols.wamp, metadata, target.constructor);
390
- };
391
- };
392
571
  }
package/tsconfig.json ADDED
@@ -0,0 +1,47 @@
1
+ // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ {
15
+ "compilerOptions": {
16
+ // Environment setup & latest features
17
+ "target": "ESNext",
18
+ "module": "ESNext",
19
+ "moduleDetection": "force",
20
+ "allowJs": true,
21
+ "types": [
22
+ "bun-types",
23
+ "type-fest"
24
+ ],
25
+ "experimentalDecorators": true,
26
+ "emitDecoratorMetadata": true,
27
+ // Bundler mode
28
+ "moduleResolution": "bundler",
29
+ "allowImportingTsExtensions": true,
30
+ "verbatimModuleSyntax": true,
31
+ "noEmit": true,
32
+ // Best practices
33
+ "strict": true,
34
+ "skipLibCheck": true,
35
+ "noFallthroughCasesInSwitch": true,
36
+ "noUncheckedIndexedAccess": false,
37
+ "forceConsistentCasingInFileNames": true,
38
+ // Some stricter flags (disabled by default)
39
+ "noUnusedLocals": true,
40
+ "noUnusedParameters": true,
41
+ "noPropertyAccessFromIndexSignature": false,
42
+ },
43
+ "exclude": [
44
+ "node_modules",
45
+ "dist",
46
+ ],
47
+ }