@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.
- package/README.md +71 -8
- package/dist/index.d.mts +474 -9
- package/dist/index.d.ts +474 -9
- package/dist/index.js +563 -200
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +562 -202
- package/dist/index.mjs.map +1 -1
- package/examples/channel-examples.ts +518 -0
- package/package.json +23 -5
- package/src/index.ts +3 -1
- package/src/lib/A-Channel/A-Channel.component.ts +581 -32
- package/src/lib/A-Channel/A-Channel.constants.ts +76 -0
- package/src/lib/A-Channel/A-Channel.error.ts +42 -1
- package/src/lib/A-Channel/A-Channel.types.ts +11 -0
- package/src/lib/A-Channel/A-ChannelRequest.context.ts +91 -0
- package/src/lib/A-Channel/README.md +864 -0
- package/tests/A-Channel.test.ts +483 -5
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
# A-Channel
|
|
2
|
+
|
|
3
|
+
A powerful, extensible communication channel component that provides structured messaging patterns with lifecycle management, error handling, and type safety for TypeScript applications.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Key Features](#key-features)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Basic Usage](#basic-usage)
|
|
11
|
+
- [Advanced Usage](#advanced-usage)
|
|
12
|
+
- [Channel Lifecycle](#channel-lifecycle)
|
|
13
|
+
- [Request Processing](#request-processing)
|
|
14
|
+
- [Send Operations](#send-operations)
|
|
15
|
+
- [Error Handling](#error-handling)
|
|
16
|
+
- [Type Safety](#type-safety)
|
|
17
|
+
- [Extension and Customization](#extension-and-customization)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [Examples](#examples)
|
|
20
|
+
|
|
21
|
+
## Overview
|
|
22
|
+
|
|
23
|
+
A-Channel implements communication patterns that allow you to encapsulate messaging operations as structured, extensible components. It provides a foundation for building various types of communication channels (HTTP, WebSocket, Message Queues, etc.) with consistent lifecycle management and error handling.
|
|
24
|
+
|
|
25
|
+
## Key Features
|
|
26
|
+
|
|
27
|
+
- 🔄 **Lifecycle Management** - Complete connection and processing lifecycle with hooks
|
|
28
|
+
- 📡 **Multiple Communication Patterns** - Request/Response and Fire-and-Forget messaging
|
|
29
|
+
- 🛡️ **Error Handling** - Comprehensive error capture and management
|
|
30
|
+
- 🎯 **Type Safety** - Full TypeScript support with generic types
|
|
31
|
+
- 🔧 **Extensible** - Component-based architecture for custom behavior
|
|
32
|
+
- ⚡ **Concurrent Processing** - Handle multiple requests simultaneously
|
|
33
|
+
- 📊 **Processing State** - Built-in state tracking and management
|
|
34
|
+
- 🏗️ **Dependency Injection** - Integration with A-Context for scope management
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install @adaas/a-utils
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Basic Usage
|
|
43
|
+
|
|
44
|
+
### Simple Channel Creation
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { A_Channel } from '@adaas/a-utils/lib/A-Channel/A-Channel.component';
|
|
48
|
+
import { A_Context } from '@adaas/a-concept';
|
|
49
|
+
|
|
50
|
+
// Create a basic channel
|
|
51
|
+
const channel = new A_Channel();
|
|
52
|
+
A_Context.root.register(channel);
|
|
53
|
+
|
|
54
|
+
// Initialize the channel
|
|
55
|
+
await channel.initialize;
|
|
56
|
+
|
|
57
|
+
console.log(`Channel ready, processing: ${channel.processing}`);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Basic Request/Response
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Send a request and get response
|
|
64
|
+
const response = await channel.request({
|
|
65
|
+
action: 'getUserData',
|
|
66
|
+
userId: '12345'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
console.log('Request params:', response.params);
|
|
70
|
+
console.log('Response data:', response.data);
|
|
71
|
+
console.log('Request status:', response.status);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Fire-and-Forget Messaging
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Send a message without waiting for response
|
|
78
|
+
await channel.send({
|
|
79
|
+
type: 'notification',
|
|
80
|
+
message: 'User logged in',
|
|
81
|
+
timestamp: new Date().toISOString()
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log('Message sent successfully');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Advanced Usage
|
|
88
|
+
|
|
89
|
+
### Custom Channel with Business Logic
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { A_Component, A_Feature, A_Inject } from '@adaas/a-concept';
|
|
93
|
+
import { A_ChannelFeatures } from '@adaas/a-utils/lib/A-Channel/A-Channel.constants';
|
|
94
|
+
import { A_ChannelRequest } from '@adaas/a-utils/lib/A-Channel/A-ChannelRequest.context';
|
|
95
|
+
|
|
96
|
+
// Define typed interfaces
|
|
97
|
+
interface UserRequest {
|
|
98
|
+
action: 'create' | 'update' | 'delete';
|
|
99
|
+
userId: string;
|
|
100
|
+
userData?: any;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface UserResponse {
|
|
104
|
+
success: boolean;
|
|
105
|
+
userId: string;
|
|
106
|
+
message: string;
|
|
107
|
+
timestamp: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create custom channel
|
|
111
|
+
class UserManagementChannel extends A_Channel {}
|
|
112
|
+
|
|
113
|
+
// Create custom processor
|
|
114
|
+
class UserProcessor extends A_Component {
|
|
115
|
+
|
|
116
|
+
@A_Feature.Extend({ scope: [UserManagementChannel] })
|
|
117
|
+
async [A_ChannelFeatures.onConnect]() {
|
|
118
|
+
console.log('User management channel connected');
|
|
119
|
+
// Initialize database connections, validate configuration, etc.
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@A_Feature.Extend({ scope: [UserManagementChannel] })
|
|
123
|
+
async [A_ChannelFeatures.onBeforeRequest](
|
|
124
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<UserRequest>
|
|
125
|
+
) {
|
|
126
|
+
// Validate request
|
|
127
|
+
const { action, userId } = context.params;
|
|
128
|
+
if (!userId) {
|
|
129
|
+
throw new Error('User ID is required');
|
|
130
|
+
}
|
|
131
|
+
console.log(`Processing ${action} for user ${userId}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@A_Feature.Extend({ scope: [UserManagementChannel] })
|
|
135
|
+
async [A_ChannelFeatures.onRequest](
|
|
136
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<UserRequest, UserResponse>
|
|
137
|
+
) {
|
|
138
|
+
const { action, userId, userData } = context.params;
|
|
139
|
+
|
|
140
|
+
// Simulate business logic
|
|
141
|
+
let message = '';
|
|
142
|
+
switch (action) {
|
|
143
|
+
case 'create':
|
|
144
|
+
message = `User ${userId} created successfully`;
|
|
145
|
+
break;
|
|
146
|
+
case 'update':
|
|
147
|
+
message = `User ${userId} updated successfully`;
|
|
148
|
+
break;
|
|
149
|
+
case 'delete':
|
|
150
|
+
message = `User ${userId} deleted successfully`;
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Set response data
|
|
155
|
+
(context as any)._result = {
|
|
156
|
+
success: true,
|
|
157
|
+
userId,
|
|
158
|
+
message,
|
|
159
|
+
timestamp: new Date().toISOString()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@A_Feature.Extend({ scope: [UserManagementChannel] })
|
|
164
|
+
async [A_ChannelFeatures.onAfterRequest](
|
|
165
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<UserRequest, UserResponse>
|
|
166
|
+
) {
|
|
167
|
+
// Log successful completion
|
|
168
|
+
console.log(`Request completed: ${context.data?.message}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@A_Feature.Extend({ scope: [UserManagementChannel] })
|
|
172
|
+
async [A_ChannelFeatures.onError](
|
|
173
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<UserRequest>
|
|
174
|
+
) {
|
|
175
|
+
console.error(`Request failed for action: ${context.params.action}`);
|
|
176
|
+
// Log error, send alerts, etc.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Usage
|
|
181
|
+
A_Context.reset();
|
|
182
|
+
A_Context.root.register(UserProcessor);
|
|
183
|
+
|
|
184
|
+
const userChannel = new UserManagementChannel();
|
|
185
|
+
A_Context.root.register(userChannel);
|
|
186
|
+
|
|
187
|
+
// Process user requests
|
|
188
|
+
const createResponse = await userChannel.request<UserRequest, UserResponse>({
|
|
189
|
+
action: 'create',
|
|
190
|
+
userId: 'user-123',
|
|
191
|
+
userData: { name: 'John Doe', email: 'john@example.com' }
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
console.log('User created:', createResponse.data);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Channel Lifecycle
|
|
198
|
+
|
|
199
|
+
A-Channel follows a structured lifecycle with multiple extension points:
|
|
200
|
+
|
|
201
|
+
### Connection Lifecycle
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
class LifecycleChannel extends A_Channel {}
|
|
205
|
+
|
|
206
|
+
class LifecycleProcessor extends A_Component {
|
|
207
|
+
|
|
208
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
209
|
+
async [A_ChannelFeatures.onConnect]() {
|
|
210
|
+
console.log('1. Channel connecting...');
|
|
211
|
+
// Initialize connections, load configuration, validate environment
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
215
|
+
async [A_ChannelFeatures.onDisconnect]() {
|
|
216
|
+
console.log('6. Channel disconnecting...');
|
|
217
|
+
// Cleanup resources, close connections, save state
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const channel = new LifecycleChannel();
|
|
222
|
+
A_Context.root.register(LifecycleProcessor);
|
|
223
|
+
A_Context.root.register(channel);
|
|
224
|
+
|
|
225
|
+
// Initialize (calls onConnect)
|
|
226
|
+
await channel.initialize;
|
|
227
|
+
|
|
228
|
+
// Use channel for requests...
|
|
229
|
+
|
|
230
|
+
// Cleanup (calls onDisconnect)
|
|
231
|
+
await channel.disconnect();
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Request Lifecycle
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
class RequestLifecycleProcessor extends A_Component {
|
|
238
|
+
|
|
239
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
240
|
+
async [A_ChannelFeatures.onBeforeRequest](
|
|
241
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
242
|
+
) {
|
|
243
|
+
console.log('2. Pre-processing request...');
|
|
244
|
+
// Validate input, authenticate, rate limiting
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
248
|
+
async [A_ChannelFeatures.onRequest](
|
|
249
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
250
|
+
) {
|
|
251
|
+
console.log('3. Processing request...');
|
|
252
|
+
// Main business logic
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
256
|
+
async [A_ChannelFeatures.onAfterRequest](
|
|
257
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
258
|
+
) {
|
|
259
|
+
console.log('4. Post-processing request...');
|
|
260
|
+
// Logging, analytics, cleanup
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@A_Feature.Extend({ scope: [LifecycleChannel] })
|
|
264
|
+
async [A_ChannelFeatures.onError](
|
|
265
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
266
|
+
) {
|
|
267
|
+
console.log('5. Handling error...');
|
|
268
|
+
// Error logging, alerts, recovery
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Request Processing
|
|
274
|
+
|
|
275
|
+
### Synchronous Request/Response
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
class APIChannel extends A_Channel {}
|
|
279
|
+
|
|
280
|
+
class APIProcessor extends A_Component {
|
|
281
|
+
|
|
282
|
+
@A_Feature.Extend({ scope: [APIChannel] })
|
|
283
|
+
async [A_ChannelFeatures.onRequest](
|
|
284
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
285
|
+
) {
|
|
286
|
+
const { endpoint, method, body } = context.params;
|
|
287
|
+
|
|
288
|
+
// Simulate API call
|
|
289
|
+
const response = await fetch(endpoint, {
|
|
290
|
+
method,
|
|
291
|
+
body: JSON.stringify(body),
|
|
292
|
+
headers: { 'Content-Type': 'application/json' }
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const data = await response.json();
|
|
296
|
+
(context as any)._result = {
|
|
297
|
+
status: response.status,
|
|
298
|
+
data,
|
|
299
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
A_Context.root.register(APIProcessor);
|
|
305
|
+
|
|
306
|
+
const apiChannel = new APIChannel();
|
|
307
|
+
A_Context.root.register(apiChannel);
|
|
308
|
+
|
|
309
|
+
const response = await apiChannel.request({
|
|
310
|
+
endpoint: 'https://api.example.com/users',
|
|
311
|
+
method: 'GET'
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
console.log('API Response:', response.data);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Concurrent Request Processing
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// Process multiple requests concurrently
|
|
321
|
+
const requests = [
|
|
322
|
+
apiChannel.request({ endpoint: '/users/1', method: 'GET' }),
|
|
323
|
+
apiChannel.request({ endpoint: '/users/2', method: 'GET' }),
|
|
324
|
+
apiChannel.request({ endpoint: '/users/3', method: 'GET' })
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
const responses = await Promise.all(requests);
|
|
328
|
+
responses.forEach((response, index) => {
|
|
329
|
+
console.log(`User ${index + 1}:`, response.data);
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Send Operations
|
|
334
|
+
|
|
335
|
+
### Event Broadcasting
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
class EventChannel extends A_Channel {}
|
|
339
|
+
|
|
340
|
+
class EventBroadcaster extends A_Component {
|
|
341
|
+
|
|
342
|
+
@A_Feature.Extend({ scope: [EventChannel] })
|
|
343
|
+
async [A_ChannelFeatures.onSend](
|
|
344
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
345
|
+
) {
|
|
346
|
+
const { eventType, payload, recipients } = context.params;
|
|
347
|
+
|
|
348
|
+
console.log(`Broadcasting ${eventType} to ${recipients.length} recipients`);
|
|
349
|
+
|
|
350
|
+
// Simulate broadcasting
|
|
351
|
+
for (const recipient of recipients) {
|
|
352
|
+
console.log(`Sending to ${recipient}:`, payload);
|
|
353
|
+
// Send to message queue, WebSocket, email service, etc.
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
A_Context.root.register(EventBroadcaster);
|
|
359
|
+
|
|
360
|
+
const eventChannel = new EventChannel();
|
|
361
|
+
A_Context.root.register(eventChannel);
|
|
362
|
+
|
|
363
|
+
// Send notification to multiple users
|
|
364
|
+
await eventChannel.send({
|
|
365
|
+
eventType: 'user.login',
|
|
366
|
+
payload: { userId: '123', timestamp: new Date() },
|
|
367
|
+
recipients: ['admin@example.com', 'user@example.com']
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Message Queuing
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
class QueueChannel extends A_Channel {}
|
|
375
|
+
|
|
376
|
+
class MessageQueue extends A_Component {
|
|
377
|
+
private queue: any[] = [];
|
|
378
|
+
|
|
379
|
+
@A_Feature.Extend({ scope: [QueueChannel] })
|
|
380
|
+
async [A_ChannelFeatures.onSend](
|
|
381
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
382
|
+
) {
|
|
383
|
+
const message = {
|
|
384
|
+
id: Date.now(),
|
|
385
|
+
payload: context.params,
|
|
386
|
+
timestamp: new Date(),
|
|
387
|
+
retries: 0
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
this.queue.push(message);
|
|
391
|
+
console.log(`Queued message ${message.id}, queue size: ${this.queue.length}`);
|
|
392
|
+
|
|
393
|
+
// Process queue asynchronously
|
|
394
|
+
setImmediate(() => this.processQueue());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private async processQueue() {
|
|
398
|
+
while (this.queue.length > 0) {
|
|
399
|
+
const message = this.queue.shift();
|
|
400
|
+
try {
|
|
401
|
+
await this.processMessage(message);
|
|
402
|
+
console.log(`Processed message ${message.id}`);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`Failed to process message ${message.id}:`, error);
|
|
405
|
+
// Implement retry logic
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private async processMessage(message: any) {
|
|
411
|
+
// Simulate message processing
|
|
412
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
A_Context.root.register(MessageQueue);
|
|
417
|
+
|
|
418
|
+
const queueChannel = new QueueChannel();
|
|
419
|
+
A_Context.root.register(queueChannel);
|
|
420
|
+
|
|
421
|
+
// Send messages to queue
|
|
422
|
+
await queueChannel.send({ type: 'email', to: 'user@example.com' });
|
|
423
|
+
await queueChannel.send({ type: 'sms', to: '+1234567890' });
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Error Handling
|
|
427
|
+
|
|
428
|
+
### Comprehensive Error Management
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { A_ChannelError } from '@adaas/a-utils/lib/A-Channel/A-Channel.error';
|
|
432
|
+
|
|
433
|
+
class RobustChannel extends A_Channel {}
|
|
434
|
+
|
|
435
|
+
class ErrorHandler extends A_Component {
|
|
436
|
+
|
|
437
|
+
@A_Feature.Extend({ scope: [RobustChannel] })
|
|
438
|
+
async [A_ChannelFeatures.onRequest](
|
|
439
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
440
|
+
) {
|
|
441
|
+
const { operation } = context.params;
|
|
442
|
+
|
|
443
|
+
switch (operation) {
|
|
444
|
+
case 'network_error':
|
|
445
|
+
throw new Error('Network connection failed');
|
|
446
|
+
case 'validation_error':
|
|
447
|
+
throw new Error('Invalid input data');
|
|
448
|
+
case 'timeout_error':
|
|
449
|
+
throw new Error('Operation timed out');
|
|
450
|
+
case 'success':
|
|
451
|
+
(context as any)._result = { success: true };
|
|
452
|
+
break;
|
|
453
|
+
default:
|
|
454
|
+
throw new Error('Unknown operation');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
@A_Feature.Extend({ scope: [RobustChannel] })
|
|
459
|
+
async [A_ChannelFeatures.onError](
|
|
460
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
461
|
+
) {
|
|
462
|
+
console.log('Error details:', {
|
|
463
|
+
operation: context.params.operation,
|
|
464
|
+
failed: context.failed,
|
|
465
|
+
status: context.status
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Implement error recovery, alerting, logging
|
|
469
|
+
if (context.params.operation === 'network_error') {
|
|
470
|
+
console.log('Implementing network retry logic...');
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
A_Context.root.register(ErrorHandler);
|
|
476
|
+
|
|
477
|
+
const robustChannel = new RobustChannel();
|
|
478
|
+
A_Context.root.register(robustChannel);
|
|
479
|
+
|
|
480
|
+
// Test error handling
|
|
481
|
+
const operations = ['success', 'network_error', 'validation_error'];
|
|
482
|
+
|
|
483
|
+
for (const operation of operations) {
|
|
484
|
+
const result = await robustChannel.request({ operation });
|
|
485
|
+
|
|
486
|
+
if (result.failed) {
|
|
487
|
+
console.log(`Operation ${operation} failed as expected`);
|
|
488
|
+
} else {
|
|
489
|
+
console.log(`Operation ${operation} succeeded:`, result.data);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Type Safety
|
|
495
|
+
|
|
496
|
+
### Strongly Typed Channels
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// Define strict interfaces
|
|
500
|
+
interface DatabaseQuery {
|
|
501
|
+
table: string;
|
|
502
|
+
operation: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
|
|
503
|
+
where?: Record<string, any>;
|
|
504
|
+
data?: Record<string, any>;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface DatabaseResult {
|
|
508
|
+
success: boolean;
|
|
509
|
+
rowsAffected: number;
|
|
510
|
+
data?: any[];
|
|
511
|
+
insertId?: number;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
class DatabaseChannel extends A_Channel {}
|
|
515
|
+
|
|
516
|
+
class DatabaseProcessor extends A_Component {
|
|
517
|
+
|
|
518
|
+
@A_Feature.Extend({ scope: [DatabaseChannel] })
|
|
519
|
+
async [A_ChannelFeatures.onRequest](
|
|
520
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest<DatabaseQuery, DatabaseResult>
|
|
521
|
+
) {
|
|
522
|
+
const { table, operation, where, data } = context.params;
|
|
523
|
+
|
|
524
|
+
// Type-safe access to parameters
|
|
525
|
+
console.log(`Executing ${operation} on table ${table}`);
|
|
526
|
+
|
|
527
|
+
// Simulate database operation
|
|
528
|
+
let result: DatabaseResult;
|
|
529
|
+
|
|
530
|
+
switch (operation) {
|
|
531
|
+
case 'SELECT':
|
|
532
|
+
result = {
|
|
533
|
+
success: true,
|
|
534
|
+
rowsAffected: 0,
|
|
535
|
+
data: [{ id: 1, name: 'Test' }]
|
|
536
|
+
};
|
|
537
|
+
break;
|
|
538
|
+
case 'INSERT':
|
|
539
|
+
result = {
|
|
540
|
+
success: true,
|
|
541
|
+
rowsAffected: 1,
|
|
542
|
+
insertId: 123
|
|
543
|
+
};
|
|
544
|
+
break;
|
|
545
|
+
default:
|
|
546
|
+
result = {
|
|
547
|
+
success: true,
|
|
548
|
+
rowsAffected: 1
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
(context as any)._result = result;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
A_Context.root.register(DatabaseProcessor);
|
|
557
|
+
|
|
558
|
+
const dbChannel = new DatabaseChannel();
|
|
559
|
+
A_Context.root.register(dbChannel);
|
|
560
|
+
|
|
561
|
+
// Type-safe requests
|
|
562
|
+
const selectResult = await dbChannel.request<DatabaseQuery, DatabaseResult>({
|
|
563
|
+
table: 'users',
|
|
564
|
+
operation: 'SELECT',
|
|
565
|
+
where: { active: true }
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// TypeScript provides full intellisense and type checking
|
|
569
|
+
if (selectResult.data?.success) {
|
|
570
|
+
console.log('Selected rows:', selectResult.data.data?.length);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const insertResult = await dbChannel.request<DatabaseQuery, DatabaseResult>({
|
|
574
|
+
table: 'users',
|
|
575
|
+
operation: 'INSERT',
|
|
576
|
+
data: { name: 'John Doe', email: 'john@example.com' }
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
if (insertResult.data?.success) {
|
|
580
|
+
console.log('Inserted user with ID:', insertResult.data.insertId);
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Extension and Customization
|
|
585
|
+
|
|
586
|
+
### Multi-Feature Channel
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
class AdvancedChannel extends A_Channel {
|
|
590
|
+
private metrics = { requests: 0, errors: 0, totalTime: 0 };
|
|
591
|
+
|
|
592
|
+
getMetrics() {
|
|
593
|
+
return { ...this.metrics };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
resetMetrics() {
|
|
597
|
+
this.metrics = { requests: 0, errors: 0, totalTime: 0 };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
class MetricsCollector extends A_Component {
|
|
602
|
+
|
|
603
|
+
@A_Feature.Extend({ scope: [AdvancedChannel] })
|
|
604
|
+
async [A_ChannelFeatures.onBeforeRequest](
|
|
605
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
606
|
+
) {
|
|
607
|
+
(context as any)._startTime = Date.now();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
@A_Feature.Extend({ scope: [AdvancedChannel] })
|
|
611
|
+
async [A_ChannelFeatures.onAfterRequest](
|
|
612
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
613
|
+
) {
|
|
614
|
+
const channel = A_Context.scope(this).resolve(AdvancedChannel);
|
|
615
|
+
const duration = Date.now() - (context as any)._startTime;
|
|
616
|
+
|
|
617
|
+
channel['metrics'].requests++;
|
|
618
|
+
channel['metrics'].totalTime += duration;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
@A_Feature.Extend({ scope: [AdvancedChannel] })
|
|
622
|
+
async [A_ChannelFeatures.onError](
|
|
623
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
624
|
+
) {
|
|
625
|
+
const channel = A_Context.scope(this).resolve(AdvancedChannel);
|
|
626
|
+
channel['metrics'].errors++;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
class CacheLayer extends A_Component {
|
|
631
|
+
private cache = new Map<string, any>();
|
|
632
|
+
|
|
633
|
+
@A_Feature.Extend({ scope: [AdvancedChannel] })
|
|
634
|
+
async [A_ChannelFeatures.onBeforeRequest](
|
|
635
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
636
|
+
) {
|
|
637
|
+
const cacheKey = JSON.stringify(context.params);
|
|
638
|
+
|
|
639
|
+
if (this.cache.has(cacheKey)) {
|
|
640
|
+
console.log('Cache hit!');
|
|
641
|
+
(context as any)._result = this.cache.get(cacheKey);
|
|
642
|
+
(context as any)._cached = true;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
@A_Feature.Extend({ scope: [AdvancedChannel] })
|
|
647
|
+
async [A_ChannelFeatures.onAfterRequest](
|
|
648
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
649
|
+
) {
|
|
650
|
+
if (!(context as any)._cached && context.data) {
|
|
651
|
+
const cacheKey = JSON.stringify(context.params);
|
|
652
|
+
this.cache.set(cacheKey, context.data);
|
|
653
|
+
console.log('Cached result');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Register multiple processors
|
|
659
|
+
A_Context.root.register(MetricsCollector);
|
|
660
|
+
A_Context.root.register(CacheLayer);
|
|
661
|
+
|
|
662
|
+
const advancedChannel = new AdvancedChannel();
|
|
663
|
+
A_Context.root.register(advancedChannel);
|
|
664
|
+
|
|
665
|
+
// Use channel with metrics and caching
|
|
666
|
+
await advancedChannel.request({ action: 'getData', id: 1 });
|
|
667
|
+
await advancedChannel.request({ action: 'getData', id: 1 }); // Cache hit
|
|
668
|
+
await advancedChannel.request({ action: 'getData', id: 2 });
|
|
669
|
+
|
|
670
|
+
console.log('Metrics:', advancedChannel.getMetrics());
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## API Reference
|
|
674
|
+
|
|
675
|
+
### A_Channel Class
|
|
676
|
+
|
|
677
|
+
#### Properties
|
|
678
|
+
- `processing: boolean` - Indicates if channel is currently processing requests
|
|
679
|
+
- `initialize: Promise<void>` - Promise that resolves when channel is initialized
|
|
680
|
+
|
|
681
|
+
#### Methods
|
|
682
|
+
|
|
683
|
+
##### Connection Management
|
|
684
|
+
- `async connect(): Promise<void>` - Initialize the channel
|
|
685
|
+
- `async disconnect(): Promise<void>` - Cleanup and disconnect the channel
|
|
686
|
+
|
|
687
|
+
##### Communication
|
|
688
|
+
- `async request<T, R>(params: T): Promise<A_ChannelRequest<T, R>>` - Send request and wait for response
|
|
689
|
+
- `async send<T>(message: T): Promise<void>` - Send fire-and-forget message
|
|
690
|
+
|
|
691
|
+
#### Lifecycle Hooks (Extensible via A_Feature)
|
|
692
|
+
- `onConnect` - Called during channel initialization
|
|
693
|
+
- `onDisconnect` - Called during channel cleanup
|
|
694
|
+
- `onBeforeRequest` - Called before processing request
|
|
695
|
+
- `onRequest` - Called to process request
|
|
696
|
+
- `onAfterRequest` - Called after processing request
|
|
697
|
+
- `onSend` - Called to process send operation
|
|
698
|
+
- `onError` - Called when any operation fails
|
|
699
|
+
|
|
700
|
+
### A_ChannelRequest Class
|
|
701
|
+
|
|
702
|
+
#### Properties
|
|
703
|
+
- `params: T` - Request parameters
|
|
704
|
+
- `data: R` - Response data (after processing)
|
|
705
|
+
- `status: A_ChannelRequestStatuses` - Request status (PENDING, SUCCESS, FAILED)
|
|
706
|
+
- `failed: boolean` - Whether the request failed
|
|
707
|
+
|
|
708
|
+
#### Methods
|
|
709
|
+
- `fail(error: Error): void` - Mark request as failed
|
|
710
|
+
|
|
711
|
+
### Constants
|
|
712
|
+
|
|
713
|
+
#### A_ChannelRequestStatuses
|
|
714
|
+
```typescript
|
|
715
|
+
enum A_ChannelRequestStatuses {
|
|
716
|
+
PENDING = 'PENDING',
|
|
717
|
+
SUCCESS = 'SUCCESS',
|
|
718
|
+
FAILED = 'FAILED'
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
## Examples
|
|
723
|
+
|
|
724
|
+
### HTTP Client Channel
|
|
725
|
+
|
|
726
|
+
```typescript
|
|
727
|
+
class HttpChannel extends A_Channel {
|
|
728
|
+
constructor(private baseUrl: string, private defaultHeaders: Record<string, string> = {}) {
|
|
729
|
+
super();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async get(path: string, headers?: Record<string, string>) {
|
|
733
|
+
return this.request({
|
|
734
|
+
method: 'GET',
|
|
735
|
+
url: `${this.baseUrl}${path}`,
|
|
736
|
+
headers: { ...this.defaultHeaders, ...headers }
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async post(path: string, body: any, headers?: Record<string, string>) {
|
|
741
|
+
return this.request({
|
|
742
|
+
method: 'POST',
|
|
743
|
+
url: `${this.baseUrl}${path}`,
|
|
744
|
+
body,
|
|
745
|
+
headers: { ...this.defaultHeaders, ...headers }
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
class HttpProcessor extends A_Component {
|
|
751
|
+
@A_Feature.Extend({ scope: [HttpChannel] })
|
|
752
|
+
async [A_ChannelFeatures.onRequest](
|
|
753
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
754
|
+
) {
|
|
755
|
+
const { method, url, body, headers } = context.params;
|
|
756
|
+
|
|
757
|
+
const response = await fetch(url, {
|
|
758
|
+
method,
|
|
759
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
760
|
+
headers: {
|
|
761
|
+
'Content-Type': 'application/json',
|
|
762
|
+
...headers
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
const data = await response.json();
|
|
767
|
+
|
|
768
|
+
(context as any)._result = {
|
|
769
|
+
status: response.status,
|
|
770
|
+
statusText: response.statusText,
|
|
771
|
+
data,
|
|
772
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Usage
|
|
778
|
+
A_Context.root.register(HttpProcessor);
|
|
779
|
+
|
|
780
|
+
const httpClient = new HttpChannel('https://api.example.com', {
|
|
781
|
+
'Authorization': 'Bearer token123'
|
|
782
|
+
});
|
|
783
|
+
A_Context.root.register(httpClient);
|
|
784
|
+
|
|
785
|
+
const userResponse = await httpClient.get('/users/123');
|
|
786
|
+
console.log('User data:', userResponse.data);
|
|
787
|
+
|
|
788
|
+
const createResponse = await httpClient.post('/users', {
|
|
789
|
+
name: 'John Doe',
|
|
790
|
+
email: 'john@example.com'
|
|
791
|
+
});
|
|
792
|
+
console.log('Created user:', createResponse.data);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### WebSocket Channel
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
class WebSocketChannel extends A_Channel {
|
|
799
|
+
private ws?: WebSocket;
|
|
800
|
+
|
|
801
|
+
async connectWebSocket(url: string) {
|
|
802
|
+
return new Promise<void>((resolve, reject) => {
|
|
803
|
+
this.ws = new WebSocket(url);
|
|
804
|
+
this.ws.onopen = () => resolve();
|
|
805
|
+
this.ws.onerror = (error) => reject(error);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
class WebSocketProcessor extends A_Component {
|
|
811
|
+
|
|
812
|
+
@A_Feature.Extend({ scope: [WebSocketChannel] })
|
|
813
|
+
async [A_ChannelFeatures.onConnect]() {
|
|
814
|
+
const channel = A_Context.scope(this).resolve(WebSocketChannel);
|
|
815
|
+
await channel.connectWebSocket('ws://localhost:8080');
|
|
816
|
+
console.log('WebSocket connected');
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
@A_Feature.Extend({ scope: [WebSocketChannel] })
|
|
820
|
+
async [A_ChannelFeatures.onSend](
|
|
821
|
+
@A_Inject(A_ChannelRequest) context: A_ChannelRequest
|
|
822
|
+
) {
|
|
823
|
+
const channel = A_Context.scope(this).resolve(WebSocketChannel);
|
|
824
|
+
const message = JSON.stringify(context.params);
|
|
825
|
+
|
|
826
|
+
if (channel['ws']?.readyState === WebSocket.OPEN) {
|
|
827
|
+
channel['ws'].send(message);
|
|
828
|
+
console.log('Message sent via WebSocket');
|
|
829
|
+
} else {
|
|
830
|
+
throw new Error('WebSocket not connected');
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Usage
|
|
836
|
+
A_Context.root.register(WebSocketProcessor);
|
|
837
|
+
|
|
838
|
+
const wsChannel = new WebSocketChannel();
|
|
839
|
+
A_Context.root.register(wsChannel);
|
|
840
|
+
|
|
841
|
+
await wsChannel.initialize;
|
|
842
|
+
|
|
843
|
+
await wsChannel.send({
|
|
844
|
+
type: 'chat',
|
|
845
|
+
message: 'Hello WebSocket!',
|
|
846
|
+
timestamp: new Date().toISOString()
|
|
847
|
+
});
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
---
|
|
851
|
+
|
|
852
|
+
## Contributing
|
|
853
|
+
|
|
854
|
+
When extending A-Channel functionality, please ensure:
|
|
855
|
+
|
|
856
|
+
1. All new features include comprehensive tests
|
|
857
|
+
2. TypeScript types are properly defined and exported
|
|
858
|
+
3. Documentation is updated for new APIs
|
|
859
|
+
4. Examples are provided for complex features
|
|
860
|
+
5. Backward compatibility is maintained
|
|
861
|
+
|
|
862
|
+
## License
|
|
863
|
+
|
|
864
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|