@adaas/a-utils 0.1.13 → 0.1.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,27 +1,111 @@
1
- import { A_Component, A_Feature } from "@adaas/a-concept";
1
+ import { A_Component, A_Context, A_Error, A_Feature, A_IdentityHelper, A_Inject, A_Scope, A_TYPES__InjectableConstructors, A_TYPES__InjectableTargets } from "@adaas/a-concept";
2
2
  import { A_ChannelError } from "./A-Channel.error";
3
+ import { A_ChannelFeatures } from "./A-Channel.constants";
4
+ import { A_ChannelRequest } from "./A-ChannelRequest.context";
5
+ import { A_Logger } from "../A-Logger/A-Logger.component";
6
+ import { A_Config } from "../A-Config/A-Config.context";
3
7
 
4
-
5
-
8
+ /**
9
+ * A-Channel - A powerful, extensible communication channel component
10
+ *
11
+ * A-Channel provides a structured approach to implementing various communication patterns
12
+ * such as HTTP clients, WebSocket connections, message queues, and other messaging systems.
13
+ * It offers a complete lifecycle management system with extensible hooks for custom behavior.
14
+ *
15
+ * ## Key Features:
16
+ * - 🔄 **Lifecycle Management** - Complete connection and processing lifecycle with hooks
17
+ * - 📡 **Multiple Communication Patterns** - Request/Response and Fire-and-Forget messaging
18
+ * - 🛡️ **Error Handling** - Comprehensive error capture and management
19
+ * - 🎯 **Type Safety** - Full TypeScript support with generic types
20
+ * - 🔧 **Extensible** - Component-based architecture for custom behavior
21
+ * - ⚡ **Concurrent Processing** - Handle multiple requests simultaneously
22
+ *
23
+ * ## Basic Usage:
24
+ * ```typescript
25
+ * const channel = new A_Channel();
26
+ * A_Context.root.register(channel);
27
+ *
28
+ * // Request/Response pattern
29
+ * const response = await channel.request({ action: 'getData', id: 123 });
30
+ *
31
+ * // Fire-and-forget pattern
32
+ * await channel.send({ type: 'notification', message: 'Hello World' });
33
+ * ```
34
+ *
35
+ * ## Custom Implementation:
36
+ * ```typescript
37
+ * class HttpChannel extends A_Channel {}
38
+ *
39
+ * class HttpProcessor extends A_Component {
40
+ * @A_Feature.Extend({ scope: [HttpChannel] })
41
+ * async [A_ChannelFeatures.onRequest](
42
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
43
+ * ) {
44
+ * const response = await fetch(context.params.url);
45
+ * (context as any)._result = await response.json();
46
+ * }
47
+ * }
48
+ * ```
49
+ *
50
+ * @see {@link ./README.md} For complete documentation and examples
51
+ */
6
52
  export class A_Channel extends A_Component {
7
53
 
8
54
  /**
9
- * Indicates whether the channel is processing requests
55
+ * Indicates whether the channel is currently processing requests.
56
+ * This flag is managed automatically during request/send operations.
57
+ *
58
+ * @readonly
10
59
  */
11
60
  protected _processing: boolean = false;
61
+
12
62
  /**
13
- * Indicates whether the channel is connected
63
+ * Promise that resolves when the channel initialization is complete.
64
+ * Ensures that onConnect lifecycle hook has been executed before
65
+ * any communication operations.
66
+ *
67
+ * @private
14
68
  */
15
69
  protected _initialized?: Promise<void>;
16
70
 
17
71
  /**
18
- * Indicates whether the channel is processing requests
19
- */
72
+ * Internal cache storage for channel-specific data.
73
+ * Can be used by custom implementations for caching responses,
74
+ * connection pools, or other channel-specific state.
75
+ *
76
+ * @protected
77
+ */
78
+ protected _cache: Map<string, any> = new Map();
79
+
80
+ /**
81
+ * Creates a new A_Channel instance.
82
+ *
83
+ * The channel must be registered with A_Context before use:
84
+ * ```typescript
85
+ * const channel = new A_Channel();
86
+ * A_Context.root.register(channel);
87
+ * ```
88
+ */
89
+ constructor() {
90
+ super();
91
+ }
92
+
93
+ /**
94
+ * Indicates whether the channel is currently processing requests.
95
+ *
96
+ * @returns {boolean} True if channel is processing, false otherwise
97
+ */
20
98
  get processing(): boolean {
21
99
  return this._processing;
22
100
  }
101
+
23
102
  /**
24
- * Indicates whether the channel is connected
103
+ * Promise that resolves when the channel is fully initialized.
104
+ *
105
+ * Automatically calls the onConnect lifecycle hook if not already called.
106
+ * This ensures the channel is ready for communication operations.
107
+ *
108
+ * @returns {Promise<void>} Promise that resolves when initialization is complete
25
109
  */
26
110
  get initialize(): Promise<void> {
27
111
  if (!this._initialized) {
@@ -30,47 +114,512 @@ export class A_Channel extends A_Component {
30
114
  return this._initialized;
31
115
  }
32
116
 
117
+ // ==========================================================
118
+ // ================ Lifecycle Extension Points =============
119
+ // ==========================================================
120
+
121
+ /**
122
+ * Connection lifecycle hook - called during channel initialization.
123
+ *
124
+ * Override this method in custom components to implement connection logic:
125
+ * - Initialize network connections
126
+ * - Load configuration
127
+ * - Validate environment
128
+ * - Set up connection pools
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * class DatabaseChannel extends A_Channel {}
133
+ *
134
+ * class DatabaseConnector extends A_Component {
135
+ * @A_Feature.Extend({ scope: [DatabaseChannel] })
136
+ * async [A_ChannelFeatures.onConnect]() {
137
+ * await this.initializeDatabase();
138
+ * console.log('Database channel connected');
139
+ * }
140
+ * }
141
+ * ```
142
+ */
143
+ @A_Feature.Extend({
144
+ name: A_ChannelFeatures.onConnect
145
+ })
146
+ async onConnect(...args: any[]) {
147
+ // Default implementation - no operation
148
+ // Override in custom implementations
149
+ }
150
+
151
+ /**
152
+ * Disconnection lifecycle hook - called during channel cleanup.
153
+ *
154
+ * Override this method in custom components to implement cleanup logic:
155
+ * - Close network connections
156
+ * - Save state
157
+ * - Release resources
158
+ * - Clear caches
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * @A_Feature.Extend({ scope: [DatabaseChannel] })
163
+ * async [A_ChannelFeatures.onDisconnect]() {
164
+ * await this.closeConnections();
165
+ * console.log('Database channel disconnected');
166
+ * }
167
+ * ```
168
+ */
169
+ @A_Feature.Extend({
170
+ name: A_ChannelFeatures.onDisconnect
171
+ })
172
+ async onDisconnect(...args: any[]) {
173
+ // Default implementation - no operation
174
+ // Override in custom implementations
175
+ }
176
+
177
+ /**
178
+ * Pre-request processing hook - called before main request processing.
179
+ *
180
+ * Use this hook for:
181
+ * - Request validation
182
+ * - Authentication
183
+ * - Rate limiting
184
+ * - Logging
185
+ * - Request transformation
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * @A_Feature.Extend({ scope: [HttpChannel] })
190
+ * async [A_ChannelFeatures.onBeforeRequest](
191
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
192
+ * ) {
193
+ * // Validate required parameters
194
+ * if (!context.params.url) {
195
+ * throw new Error('URL is required');
196
+ * }
197
+ * }
198
+ * ```
199
+ */
200
+ @A_Feature.Extend({
201
+ name: A_ChannelFeatures.onBeforeRequest
202
+ })
203
+ async onBeforeRequest(...args: any[]) {
204
+ // Default implementation - no operation
205
+ // Override in custom implementations
206
+ }
207
+
208
+ /**
209
+ * Main request processing hook - core business logic goes here.
210
+ *
211
+ * This is where the main communication logic should be implemented:
212
+ * - Make HTTP requests
213
+ * - Send messages to queues
214
+ * - Execute database queries
215
+ * - Process business logic
216
+ *
217
+ * Set the result in the context: `(context as any)._result = yourResult;`
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * @A_Feature.Extend({ scope: [HttpChannel] })
222
+ * async [A_ChannelFeatures.onRequest](
223
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
224
+ * ) {
225
+ * const response = await fetch(context.params.url);
226
+ * (context as any)._result = await response.json();
227
+ * }
228
+ * ```
229
+ */
230
+ @A_Feature.Extend({
231
+ name: A_ChannelFeatures.onRequest
232
+ })
233
+ async onRequest(...args: any[]) {
234
+ // Default implementation - no operation
235
+ // Override in custom implementations
236
+ }
237
+
238
+ /**
239
+ * Post-request processing hook - called after successful request processing.
240
+ *
241
+ * Use this hook for:
242
+ * - Response transformation
243
+ * - Logging
244
+ * - Analytics
245
+ * - Caching results
246
+ * - Cleanup
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * @A_Feature.Extend({ scope: [HttpChannel] })
251
+ * async [A_ChannelFeatures.onAfterRequest](
252
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
253
+ * ) {
254
+ * console.log(`Request completed in ${Date.now() - context.startTime}ms`);
255
+ * await this.cacheResponse(context.params, context.data);
256
+ * }
257
+ * ```
258
+ */
259
+ @A_Feature.Extend({
260
+ name: A_ChannelFeatures.onAfterRequest
261
+ })
262
+ async onAfterRequest(...args: any[]) {
263
+ // Default implementation - no operation
264
+ // Override in custom implementations
265
+ }
266
+
267
+ /**
268
+ * Error handling hook - called when any operation fails.
269
+ *
270
+ * Use this hook for:
271
+ * - Error logging
272
+ * - Error transformation
273
+ * - Alerting
274
+ * - Retry logic
275
+ * - Fallback mechanisms
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * @A_Feature.Extend({ scope: [HttpChannel] })
280
+ * async [A_ChannelFeatures.onError](
281
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
282
+ * ) {
283
+ * console.error('Request failed:', context.params, context.failed);
284
+ * await this.logError(context);
285
+ * await this.sendAlert(context);
286
+ * }
287
+ * ```
288
+ */
289
+ @A_Feature.Extend({
290
+ name: A_ChannelFeatures.onError
291
+ })
292
+ async onError(...args: any[]) {
293
+ // Default implementation - no operation
294
+ // Override in custom implementations
295
+ }
296
+
297
+ /**
298
+ * Send operation hook - called for fire-and-forget messaging.
299
+ *
300
+ * Use this hook for:
301
+ * - Message broadcasting
302
+ * - Event publishing
303
+ * - Notification sending
304
+ * - Queue operations
305
+ *
306
+ * @example
307
+ * ```typescript
308
+ * @A_Feature.Extend({ scope: [EventChannel] })
309
+ * async [A_ChannelFeatures.onSend](
310
+ * @A_Inject(A_ChannelRequest) context: A_ChannelRequest
311
+ * ) {
312
+ * const { eventType, payload } = context.params;
313
+ * await this.publishEvent(eventType, payload);
314
+ * }
315
+ * ```
316
+ */
317
+ @A_Feature.Extend({
318
+ name: A_ChannelFeatures.onSend
319
+ })
320
+ async onSend(...args: any[]) {
321
+ // Default implementation - no operation
322
+ // Override in custom implementations
323
+ }
324
+
325
+ // ==========================================================
326
+ // ================= Public API Methods ===================
327
+ // ==========================================================
33
328
 
34
- @A_Feature.Define()
35
329
  /**
36
- * Initializes the channel before use
330
+ * Initializes the channel by calling the onConnect lifecycle hook.
331
+ *
332
+ * This method is called automatically when accessing the `initialize` property.
333
+ * You can also call it manually if needed.
334
+ *
335
+ * @returns {Promise<void>} Promise that resolves when connection is established
37
336
  */
38
337
  async connect() {
39
- throw new A_ChannelError(
40
- A_ChannelError.MethodNotImplemented,
41
- `The connect method is not implemented in ${this.constructor.name} channel. This method is required to initialize the channel before use. So please implement it in the derived class.`
42
- );
338
+ await this.call(A_ChannelFeatures.onConnect);
43
339
  }
44
340
 
341
+ /**
342
+ * Disconnects the channel by calling the onDisconnect lifecycle hook.
343
+ *
344
+ * Use this method to properly cleanup resources when the channel is no longer needed.
345
+ *
346
+ * @returns {Promise<void>} Promise that resolves when cleanup is complete
347
+ */
348
+ async disconnect() {
349
+ await this.call(A_ChannelFeatures.onDisconnect);
350
+ }
45
351
 
46
- @A_Feature.Define()
47
352
  /**
48
- * Allows to send a request through the channel
49
- *
50
- * @param req - The request parameters
51
- * @returns The response from the channel
353
+ * Sends a request and waits for a response (Request/Response pattern).
354
+ *
355
+ * This method follows the complete request lifecycle:
356
+ * 1. Ensures channel is initialized
357
+ * 2. Creates request scope and context
358
+ * 3. Calls onBeforeRequest hook
359
+ * 4. Calls onRequest hook (main processing)
360
+ * 5. Calls onAfterRequest hook
361
+ * 6. Returns the response context
362
+ *
363
+ * If any step fails, the onError hook is called and the error is captured
364
+ * in the returned context.
365
+ *
366
+ * @template _ParamsType The type of request parameters
367
+ * @template _ResultType The type of response data
368
+ * @param params The request parameters
369
+ * @returns {Promise<A_ChannelRequest<_ParamsType, _ResultType>>} Request context with response
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * // Basic usage
374
+ * const response = await channel.request({ action: 'getData', id: 123 });
375
+ *
376
+ * // Typed usage
377
+ * interface UserRequest { userId: string; }
378
+ * interface UserResponse { name: string; email: string; }
379
+ *
380
+ * const userResponse = await channel.request<UserRequest, UserResponse>({
381
+ * userId: 'user-123'
382
+ * });
383
+ *
384
+ * if (!userResponse.failed) {
385
+ * console.log('User:', userResponse.data.name);
386
+ * }
387
+ * ```
52
388
  */
53
- async request(params: any): Promise<any> {
54
- throw new A_ChannelError(
55
- A_ChannelError.MethodNotImplemented,
56
- `The request method is not implemented in ${this.constructor.name} channel.`
57
- );
389
+ async request<
390
+ _ParamsType extends Record<string, any> = Record<string, any>,
391
+ _ResultType extends Record<string, any> = Record<string, any>,
392
+ >(params: _ParamsType): Promise<A_ChannelRequest<_ParamsType, _ResultType>> {
393
+ // Ensure channel is initialized before processing
394
+ await this.initialize;
395
+
396
+ // Set processing flag
397
+ this._processing = true;
398
+
399
+ // Create isolated scope for this request
400
+ const requestScope = new A_Scope({
401
+ name: `a-channel@scope:request:${A_IdentityHelper.generateTimeId()}`
402
+ });
403
+
404
+ // Create request context
405
+ const context = new A_ChannelRequest<_ParamsType, _ResultType>(params);
406
+
407
+ try {
408
+ // Set up dependency injection scope
409
+ requestScope.inherit(A_Context.scope(this));
410
+ requestScope.register(context);
411
+
412
+ // Execute request lifecycle
413
+ await this.call(A_ChannelFeatures.onBeforeRequest, requestScope);
414
+ await this.call(A_ChannelFeatures.onRequest, requestScope);
415
+ await this.call(A_ChannelFeatures.onAfterRequest, requestScope);
416
+
417
+ this._processing = false;
418
+ return context;
419
+
420
+ } catch (error) {
421
+ this._processing = false;
422
+
423
+ // Create channel-specific error
424
+ const channelError = new A_ChannelError(error);
425
+ context.fail(channelError);
426
+
427
+ // Call error handling hook
428
+ await this.call(A_ChannelFeatures.onError, requestScope);
429
+
430
+ return context;
431
+ }
58
432
  }
59
433
 
434
+ /**
435
+ * Sends a fire-and-forget message (Send pattern).
436
+ *
437
+ * This method is used for one-way communication where no response is expected:
438
+ * - Event broadcasting
439
+ * - Notification sending
440
+ * - Message queuing
441
+ * - Logging operations
442
+ *
443
+ * The method follows this lifecycle:
444
+ * 1. Ensures channel is initialized
445
+ * 2. Creates send scope and context
446
+ * 3. Calls onSend hook
447
+ * 4. Completes without returning data
448
+ *
449
+ * If the operation fails, the onError hook is called but no error is thrown
450
+ * to the caller (fire-and-forget semantics).
451
+ *
452
+ * @template _ParamsType The type of message parameters
453
+ * @param message The message to send
454
+ * @returns {Promise<void>} Promise that resolves when send is complete
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * // Send notification
459
+ * await channel.send({
460
+ * type: 'user.login',
461
+ * userId: 'user-123',
462
+ * timestamp: new Date().toISOString()
463
+ * });
464
+ *
465
+ * // Send to message queue
466
+ * await channel.send({
467
+ * queue: 'email-queue',
468
+ * payload: {
469
+ * to: 'user@example.com',
470
+ * subject: 'Welcome!',
471
+ * body: 'Welcome to our service!'
472
+ * }
473
+ * });
474
+ * ```
475
+ */
476
+ async send<_ParamsType extends Record<string, any> = Record<string, any>>(
477
+ message: _ParamsType
478
+ ): Promise<void> {
479
+ // Ensure channel is initialized before processing
480
+ await this.initialize;
481
+
482
+ // Set processing flag
483
+ this._processing = true;
484
+
485
+ // Create isolated scope for this send operation
486
+ const requestScope = new A_Scope({
487
+ name: `a-channel@scope:send:${A_IdentityHelper.generateTimeId()}`
488
+ });
489
+
490
+ // Create request context for the message
491
+ const context = new A_ChannelRequest<_ParamsType>(message);
492
+
493
+ try {
494
+ // Set up dependency injection scope
495
+ requestScope.inherit(A_Context.scope(this));
496
+ requestScope.register(context);
497
+
498
+ // Execute send operation
499
+ await this.call(A_ChannelFeatures.onSend, requestScope);
500
+
501
+ this._processing = false;
502
+
503
+ } catch (error) {
504
+ this._processing = false;
505
+
506
+ // Create channel-specific error
507
+ const channelError = new A_ChannelError(error);
508
+ context.fail(channelError);
60
509
 
510
+ // Call error handling hook
511
+ await this.call(A_ChannelFeatures.onError, requestScope);
512
+
513
+ // Note: We don't re-throw the error for fire-and-forget operations
514
+ // The error is handled by the onError hook
515
+ }
516
+ }
61
517
 
62
- @A_Feature.Define()
63
518
  /**
64
- * Uses for Fire-and-Forget messaging through the channel
519
+ * @deprecated This method is deprecated and will be removed in future versions.
520
+ * Use request() or send() methods instead depending on your communication pattern.
65
521
  *
66
- * @param message - can be of any type depending on the channel implementation
522
+ * For request/response pattern: Use request()
523
+ * For fire-and-forget pattern: Use send()
524
+ * For consumer patterns: Implement custom consumer logic using request() in a loop
67
525
  */
68
- async send(message: any): Promise<void> {
69
- throw new A_ChannelError(
70
- A_ChannelError.MethodNotImplemented,
71
- `The send method is not implemented in ${this.constructor.name} channel.`
72
- );
526
+ async consume<T extends Record<string, any> = Record<string, any>>(): Promise<A_ChannelRequest<any, T>> {
527
+ await this.initialize;
528
+
529
+ this._processing = true;
530
+
531
+ const requestScope = new A_Scope({ name: `a-channel@scope:consume:${A_IdentityHelper.generateTimeId()}` });
532
+
533
+ const context = new A_ChannelRequest<any, T>();
534
+
535
+ try {
536
+ requestScope.inherit(A_Context.scope(this));
537
+ requestScope.register(context);
538
+
539
+
540
+ await this.call(A_ChannelFeatures.onConsume, requestScope);
541
+
542
+ this._processing = false;
543
+
544
+ return context
545
+
546
+ } catch (error) {
547
+
548
+ this._processing = false;
549
+
550
+ const channelError = new A_ChannelError(error)
551
+
552
+ context.fail(channelError);
553
+
554
+ await this.call(A_ChannelFeatures.onError, requestScope);
555
+
556
+ return context;
557
+ }
558
+ }
559
+
560
+ }
561
+
562
+
563
+
564
+
565
+ class HttpChannel extends A_Channel {
566
+
567
+ protected baseUrl!: string
568
+ }
569
+
570
+
571
+ class SSOChannel extends HttpChannel {
572
+ constructor() {
573
+ super();
574
+
575
+ this.baseUrl = 'https://sso.example.com';
73
576
  }
577
+ }
578
+
579
+
580
+ class PrstChannel extends HttpChannel {
74
581
 
582
+ constructor() {
583
+ super();
584
+
585
+ this.baseUrl = 'https://prst.example.com';
586
+ }
75
587
  }
76
588
 
589
+
590
+ class PollyspotChannel extends HttpChannel {
591
+
592
+ constructor() {
593
+ super();
594
+
595
+ this.baseUrl = 'https://pollyspot.example.com';
596
+ }
597
+ }
598
+
599
+
600
+ class GlobalErrorhandler extends A_Component {
601
+
602
+ @A_Feature.Extend({
603
+ name: A_ChannelFeatures.onError,
604
+ scope: [PollyspotChannel]
605
+ })
606
+ async handleError(
607
+ @A_Inject(A_ChannelRequest) context: A_ChannelRequest<any, any>,
608
+ @A_Inject(A_Logger) logger: A_Logger,
609
+ @A_Inject(A_Config) config: A_Config
610
+ ) {
611
+ // Handle the error
612
+ }
613
+
614
+ @A_Feature.Extend({
615
+ name: A_ChannelFeatures.onError,
616
+ })
617
+ async anotherError(
618
+ @A_Inject(A_ChannelRequest) context: A_ChannelRequest<any, any>,
619
+ @A_Inject(A_Logger) logger: A_Logger,
620
+ @A_Inject(A_Config) config: A_Config
621
+ ) {
622
+ // Handle the error
623
+
624
+ }
625
+ }