@adaas/a-utils 0.1.12 → 0.1.14

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