@adaas/a-utils 0.1.29 → 0.1.30
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/LICENSE +1 -1
- package/dist/index.cjs +18 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +402 -21
- package/dist/index.d.ts +402 -21
- package/dist/index.mjs +18 -28
- package/dist/index.mjs.map +1 -1
- package/examples/A-Logger-examples.ts +101 -24
- package/package.json +5 -5
- package/src/index.ts +16 -0
- package/src/lib/A-Logger/A-Logger.component.ts +385 -70
- package/src/lib/A-Logger/A-Logger.constants.ts +13 -0
- package/src/lib/A-Logger/A-Logger.types.ts +12 -1
- package/src/lib/A-Logger/README.md +116 -12
- package/src/lib/A-Memory/A-Memory.component.ts +0 -3
- package/src/lib/A-Route/A-Route.entity.ts +136 -0
- package/src/lib/A-Route/A-Route.types.ts +1 -0
- package/src/lib/A-Signal/A-Signal.constants.ts +10 -0
- package/src/lib/A-Signal/A-Signal.error.ts +0 -0
- package/src/lib/A-Signal/A-Signal.types.ts +47 -0
- package/src/lib/A-Signal/components/A-SignalBus.component.ts +103 -0
- package/src/lib/A-Signal/context/A-SignalConfig.context.ts +81 -0
- package/src/lib/A-Signal/context/A-SignalState.context.ts +197 -0
- package/src/lib/A-Signal/entities/A-Signal.entity.ts +42 -0
- package/src/lib/A-Signal/entities/A-SignalVector.entity.ts +83 -0
- package/tests/A-Logger.test.ts +90 -5
- package/tests/A-Route.test.ts +34 -0
- package/tests/A-Signal.test.ts +66 -0
- package/examples/config.ts +0 -33
|
@@ -16,9 +16,12 @@ The A_Logger component provides comprehensive logging capabilities with:
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
18
|
### 🎨 Visual Output
|
|
19
|
-
- **
|
|
19
|
+
- **Extended Color Palette**: Support for 25+ colors including basic colors (red, green, blue) and extended palette (indigo, violet, cornflower, etc.)
|
|
20
|
+
- **Color Enum Support**: Type-safe color specification using A_LoggerColorName enum
|
|
20
21
|
- **Consistent Alignment**: All log messages align properly regardless of scope name length
|
|
21
22
|
- **Structured Formatting**: Multi-line messages with proper indentation and separators
|
|
23
|
+
- **Terminal Width Detection**: Automatic detection of terminal width for optimal formatting
|
|
24
|
+
- **Responsive Text Wrapping**: Intelligent text wrapping based on terminal size
|
|
22
25
|
|
|
23
26
|
### 📊 Data Types Support
|
|
24
27
|
- **Strings**: Simple text messages with optional color coding
|
|
@@ -31,6 +34,8 @@ The A_Logger component provides comprehensive logging capabilities with:
|
|
|
31
34
|
- **Log Levels**: debug, info, warn, error, all
|
|
32
35
|
- **Environment Variables**: Configurable via A_LOGGER_LEVEL environment variable
|
|
33
36
|
- **Scope Integration**: Seamless integration with A_Scope for context
|
|
37
|
+
- **Terminal Adaptation**: Automatic adaptation to different terminal widths and environments
|
|
38
|
+
- **Browser Compatibility**: Optimized formatting for both terminal and browser console environments
|
|
34
39
|
|
|
35
40
|
### ⚡ Performance
|
|
36
41
|
- **Efficient Formatting**: Optimized for large objects and rapid logging
|
|
@@ -56,10 +61,18 @@ const scope = new A_Scope({ name: 'MyService' });
|
|
|
56
61
|
const logger = new A_Logger(scope);
|
|
57
62
|
|
|
58
63
|
// Basic logging
|
|
59
|
-
logger.
|
|
60
|
-
logger.
|
|
64
|
+
logger.info('Application started');
|
|
65
|
+
logger.info('green', 'Operation successful');
|
|
61
66
|
logger.warning('Resource usage high');
|
|
62
67
|
logger.error('Database connection failed');
|
|
68
|
+
|
|
69
|
+
// Enhanced color support with type safety
|
|
70
|
+
import { A_LoggerColorName } from "@adaas/a-utils";
|
|
71
|
+
|
|
72
|
+
logger.info('brightBlue', 'Enhanced blue message');
|
|
73
|
+
logger.info('indigo', 'Deep indigo notification');
|
|
74
|
+
logger.info('cornflower', 'Cornflower blue status');
|
|
75
|
+
logger.debug('gray', 'Debug information');
|
|
63
76
|
```
|
|
64
77
|
|
|
65
78
|
### Object Logging
|
|
@@ -75,10 +88,10 @@ const user = {
|
|
|
75
88
|
}
|
|
76
89
|
};
|
|
77
90
|
|
|
78
|
-
logger.
|
|
91
|
+
logger.info('deepBlue', 'User data:', user);
|
|
79
92
|
|
|
80
93
|
// Multi-argument logging
|
|
81
|
-
logger.
|
|
94
|
+
logger.info('green',
|
|
82
95
|
'Processing complete:',
|
|
83
96
|
'Records:', 150,
|
|
84
97
|
'Errors:', 2,
|
|
@@ -124,11 +137,11 @@ class UserService extends A_Component {
|
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
async createUser(userData: any) {
|
|
127
|
-
this.logger.
|
|
140
|
+
this.logger.info('brightBlue', 'Creating user:', userData);
|
|
128
141
|
|
|
129
142
|
try {
|
|
130
143
|
// User creation logic
|
|
131
|
-
this.logger.
|
|
144
|
+
this.logger.info('green', 'User created successfully');
|
|
132
145
|
} catch (error) {
|
|
133
146
|
this.logger.error('User creation failed:', error);
|
|
134
147
|
throw error;
|
|
@@ -169,7 +182,7 @@ const services = [
|
|
|
169
182
|
];
|
|
170
183
|
|
|
171
184
|
services.forEach(logger => {
|
|
172
|
-
logger.
|
|
185
|
+
logger.info('green', 'Service initialized');
|
|
173
186
|
logger.warning('Configuration check needed');
|
|
174
187
|
});
|
|
175
188
|
|
|
@@ -180,11 +193,85 @@ services.forEach(logger => {
|
|
|
180
193
|
// [A ] |15:42:126| Service initialized
|
|
181
194
|
```
|
|
182
195
|
|
|
196
|
+
### Terminal Width and Text Wrapping
|
|
197
|
+
|
|
198
|
+
The A_Logger automatically detects your terminal width and wraps long messages for optimal readability:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Long messages are automatically wrapped
|
|
202
|
+
logger.info('cyan',
|
|
203
|
+
'This is a very long message that will be automatically wrapped based on your terminal width to ensure optimal readability while maintaining proper formatting and indentation throughout the entire message.'
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Multi-argument messages maintain proper indentation
|
|
207
|
+
logger.info('purple',
|
|
208
|
+
'First argument that is quite long and demonstrates wrapping behavior',
|
|
209
|
+
'Second argument with continued proper indentation',
|
|
210
|
+
{
|
|
211
|
+
configuration: {
|
|
212
|
+
terminalWidth: 'auto-detected',
|
|
213
|
+
wrapping: 'intelligent',
|
|
214
|
+
fallbacks: ['80 chars', 'browser: 120 chars']
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Terminal information logging
|
|
220
|
+
logger.info('steelBlue', 'Terminal info:', {
|
|
221
|
+
columns: process.stdout?.columns || 'Not detected',
|
|
222
|
+
environment: process.stdout?.isTTY ? 'TTY' : 'Non-TTY',
|
|
223
|
+
platform: process.platform
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Terminal Width Features:**
|
|
228
|
+
- **Auto-detection**: Automatically detects terminal width using `process.stdout.columns`
|
|
229
|
+
- **Smart Wrapping**: Wraps text at word boundaries when possible
|
|
230
|
+
- **Consistent Indentation**: Maintains proper indentation for wrapped lines
|
|
231
|
+
- **Environment Aware**: Different defaults for Node.js terminal vs browser console
|
|
232
|
+
- **Fallback Support**: Uses sensible defaults when width cannot be detected
|
|
233
|
+
|
|
183
234
|
## Color Reference
|
|
184
235
|
|
|
236
|
+
### Basic Colors
|
|
185
237
|
| Color | Use Case | Code |
|
|
186
238
|
|---------|----------|------|
|
|
239
|
+
| `red` | Errors, critical issues | 31 |
|
|
240
|
+
| `yellow` | Warnings, cautions | 33 |
|
|
187
241
|
| `green` | Success, completion | 32 |
|
|
242
|
+
| `blue` | Information, general | 34 |
|
|
243
|
+
| `cyan` | Headers, highlights | 36 |
|
|
244
|
+
| `magenta` | Special emphasis | 35 |
|
|
245
|
+
| `gray` | Debug, less important | 90 |
|
|
246
|
+
|
|
247
|
+
### Extended Color Palette
|
|
248
|
+
| Color | Description | Code |
|
|
249
|
+
|---------|-------------|------|
|
|
250
|
+
| `brightBlue` | Enhanced blue variant | 94 |
|
|
251
|
+
| `brightCyan` | Enhanced cyan variant | 96 |
|
|
252
|
+
| `brightMagenta` | Enhanced magenta variant | 95 |
|
|
253
|
+
| `indigo` | Deep indigo | 38;5;54 |
|
|
254
|
+
| `violet` | Violet | 38;5;93 |
|
|
255
|
+
| `purple` | Purple | 38;5;129 |
|
|
256
|
+
| `lavender` | Lavender | 38;5;183 |
|
|
257
|
+
| `skyBlue` | Sky blue | 38;5;117 |
|
|
258
|
+
| `steelBlue` | Steel blue | 38;5;67 |
|
|
259
|
+
| `slateBlue` | Slate blue | 38;5;62 |
|
|
260
|
+
| `deepBlue` | Deep blue | 38;5;18 |
|
|
261
|
+
| `lightBlue` | Light blue | 38;5;153 |
|
|
262
|
+
| `periwinkle` | Periwinkle | 38;5;111 |
|
|
263
|
+
| `cornflower` | Cornflower blue | 38;5;69 |
|
|
264
|
+
| `powder` | Powder blue | 38;5;152 |
|
|
265
|
+
|
|
266
|
+
### Grayscale Colors
|
|
267
|
+
| Color | Description | Code |
|
|
268
|
+
|---------|-------------|------|
|
|
269
|
+
| `darkGray` | Dark gray | 30 |
|
|
270
|
+
| `lightGray` | Light gray | 37 |
|
|
271
|
+
| `charcoal` | Charcoal | 38;5;236 |
|
|
272
|
+
| `silver` | Silver | 38;5;250 |
|
|
273
|
+
| `smoke` | Smoke gray | 38;5;244 |
|
|
274
|
+
| `slate` | Slate gray | 38;5;240 |
|
|
188
275
|
| `blue` | Info, general messages | 34 |
|
|
189
276
|
| `red` | Errors, critical issues | 31 |
|
|
190
277
|
| `yellow` | Warnings, caution | 33 |
|
|
@@ -318,15 +405,15 @@ Padded scope name for consistent alignment.
|
|
|
318
405
|
|
|
319
406
|
### 1. Use Appropriate Colors
|
|
320
407
|
```typescript
|
|
321
|
-
logger.
|
|
322
|
-
logger.
|
|
408
|
+
logger.info('green', 'Operation successful'); // Success
|
|
409
|
+
logger.info('brightBlue', 'Processing data...'); // Info
|
|
323
410
|
logger.warning('Resource usage high'); // Warning
|
|
324
411
|
logger.error('Operation failed'); // Error
|
|
325
412
|
```
|
|
326
413
|
|
|
327
414
|
### 2. Provide Context
|
|
328
415
|
```typescript
|
|
329
|
-
logger.
|
|
416
|
+
logger.info('steelBlue', 'User operation:', {
|
|
330
417
|
userId: user.id,
|
|
331
418
|
operation: 'profile-update',
|
|
332
419
|
timestamp: new Date().toISOString()
|
|
@@ -344,7 +431,7 @@ A_LOGGER_LEVEL=warn
|
|
|
344
431
|
|
|
345
432
|
### 4. Structure Multi-argument Logs
|
|
346
433
|
```typescript
|
|
347
|
-
logger.
|
|
434
|
+
logger.info('green',
|
|
348
435
|
'Operation completed:',
|
|
349
436
|
'Duration:', `${duration}ms`,
|
|
350
437
|
'Records processed:', count,
|
|
@@ -352,6 +439,17 @@ logger.log('green',
|
|
|
352
439
|
);
|
|
353
440
|
```
|
|
354
441
|
|
|
442
|
+
### 5. Leverage Terminal Width Awareness
|
|
443
|
+
```typescript
|
|
444
|
+
// Long messages are automatically wrapped
|
|
445
|
+
logger.info('cyan', 'Very long status message that will automatically wrap based on terminal width while maintaining proper formatting');
|
|
446
|
+
|
|
447
|
+
// Use appropriate colors from the extended palette
|
|
448
|
+
logger.info('indigo', 'Service initialization complete');
|
|
449
|
+
logger.debug('charcoal', 'Detailed debug information');
|
|
450
|
+
logger.info('cornflower', 'Processing pipeline status');
|
|
451
|
+
```
|
|
452
|
+
|
|
355
453
|
## Troubleshooting
|
|
356
454
|
|
|
357
455
|
### Common Issues
|
|
@@ -368,6 +466,12 @@ A: Ensure your terminal supports ANSI color codes. Most modern terminals do.
|
|
|
368
466
|
**Q: Performance issues with large objects**
|
|
369
467
|
A: The component is optimized for large objects. If issues persist, consider logging object summaries instead of full objects.
|
|
370
468
|
|
|
469
|
+
**Q: Messages not wrapping correctly in terminal**
|
|
470
|
+
A: The logger automatically detects terminal width via `process.stdout.columns`. If detection fails, it uses defaults (80 chars for terminal, 120 for browser).
|
|
471
|
+
|
|
472
|
+
**Q: Text wrapping behavior inconsistent**
|
|
473
|
+
A: Terminal width detection depends on the environment. In non-TTY environments or when columns cannot be detected, the logger falls back to default widths.
|
|
474
|
+
|
|
371
475
|
## Contributing
|
|
372
476
|
|
|
373
477
|
When contributing to the A-Logger component:
|
|
@@ -103,9 +103,6 @@ export class A_Memory<
|
|
|
103
103
|
@A_Inject(A_Scope) scope: A_Scope,
|
|
104
104
|
...args: any[]
|
|
105
105
|
): Promise<void> {
|
|
106
|
-
console.log('A_Memory onSet called with key:', scope.name);
|
|
107
|
-
console.log('A_Memory onSet called with key:', scope.parent?.name);
|
|
108
|
-
|
|
109
106
|
// Handle set operation
|
|
110
107
|
context.set(operation.params.key, operation.params.value);
|
|
111
108
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { A_Fragment } from '@adaas/a-concept';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class A_Route<
|
|
6
|
+
_TParams extends Record<string, any> = Record<string, any>,
|
|
7
|
+
_TQuery extends Record<string, any> = Record<string, any>
|
|
8
|
+
> extends A_Fragment {
|
|
9
|
+
|
|
10
|
+
public url!: string;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
url: string | RegExp,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
this.url = url instanceof RegExp ? url.source : url;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns path only without query and hash
|
|
22
|
+
*/
|
|
23
|
+
get path(): string {
|
|
24
|
+
const p = this.url.split('?')[0].split('#')[0];
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// ensure that last char is not /
|
|
28
|
+
// and remove protocol and domain if present
|
|
29
|
+
if (p.includes('://')) {
|
|
30
|
+
const pathStartIndex = p.indexOf('/', p.indexOf('://') + 3);
|
|
31
|
+
if (pathStartIndex === -1) {
|
|
32
|
+
return '/';
|
|
33
|
+
} else {
|
|
34
|
+
const path = p.slice(pathStartIndex);
|
|
35
|
+
return path.endsWith('/') ? path.slice(0, -1) : path;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return p.endsWith('/') ? p.slice(0, -1) : p;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Returns array of parameter names in the route path
|
|
42
|
+
*/
|
|
43
|
+
get params(): string[] {
|
|
44
|
+
return this.path
|
|
45
|
+
.match(/:([^\/]+)/g)
|
|
46
|
+
?.map((param) => param.slice(1))
|
|
47
|
+
|| [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns protocol based on URL scheme
|
|
53
|
+
*/
|
|
54
|
+
get protocol(): string {
|
|
55
|
+
switch (true) {
|
|
56
|
+
case this.url.startsWith('http://'):
|
|
57
|
+
return 'http';
|
|
58
|
+
case this.url.startsWith('https://'):
|
|
59
|
+
return 'https';
|
|
60
|
+
case this.url.startsWith('ws://'):
|
|
61
|
+
return 'ws';
|
|
62
|
+
case this.url.startsWith('wss://'):
|
|
63
|
+
return 'wss';
|
|
64
|
+
default:
|
|
65
|
+
return this.url.includes('://') ? this.url.split('://')[0] : 'http';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
extractParams(url: string): _TParams {
|
|
71
|
+
// Remove query string (anything after ?)
|
|
72
|
+
const cleanUrl = url.split('?')[0];
|
|
73
|
+
|
|
74
|
+
const urlSegments = cleanUrl.split('/').filter(Boolean);
|
|
75
|
+
const maskSegments = this.path.split('/').filter(Boolean);
|
|
76
|
+
|
|
77
|
+
const params = {};
|
|
78
|
+
|
|
79
|
+
for (let i = 0; i < maskSegments.length; i++) {
|
|
80
|
+
|
|
81
|
+
const maskSegment = maskSegments[i];
|
|
82
|
+
const urlSegment = urlSegments[i];
|
|
83
|
+
|
|
84
|
+
if (maskSegment.startsWith(':')) {
|
|
85
|
+
const paramName = maskSegment.slice(1); // Remove ':' from mask
|
|
86
|
+
params[paramName] = urlSegment;
|
|
87
|
+
} else if (maskSegment !== urlSegment) {
|
|
88
|
+
// If static segments don’t match → fail
|
|
89
|
+
return {} as _TParams;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return params as _TParams;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
extractQuery(url: string): _TQuery {
|
|
97
|
+
const query: Record<string, string> = {};
|
|
98
|
+
|
|
99
|
+
// Take only the part after "?"
|
|
100
|
+
const queryString = url.split('?')[1];
|
|
101
|
+
if (!queryString) return query as _TQuery;
|
|
102
|
+
|
|
103
|
+
// Remove fragment (#...) if present
|
|
104
|
+
const cleanQuery = queryString.split('#')[0];
|
|
105
|
+
|
|
106
|
+
// Split into key=value pairs
|
|
107
|
+
for (const pair of cleanQuery.split('&')) {
|
|
108
|
+
if (!pair) continue;
|
|
109
|
+
const [key, value = ''] = pair.split('=');
|
|
110
|
+
query[decodeURIComponent(key)] = decodeURIComponent(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return query as _TQuery;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
toString(): string {
|
|
119
|
+
// path can be like /api/v1/users/:id
|
|
120
|
+
// and because of that :id we need to replace it with regex that matches chars and numbers only
|
|
121
|
+
return `${this.path}`;
|
|
122
|
+
// .replace(/\/:([^\/]+)/g, '\\/([^\/]+)')
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
toRegExp(): RegExp {
|
|
126
|
+
return new RegExp(`^${this.path.replace(/\/:([^\/]+)/g, '/([^/]+)')}$`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
toAFeatureExtension(extensionScope: Array<string> = []): RegExp {
|
|
130
|
+
return new RegExp(`^${extensionScope.length
|
|
131
|
+
? `(${extensionScope.join('|')})`
|
|
132
|
+
: '.*'
|
|
133
|
+
}\\.${this.path.replace(/\/:([^\/]+)/g, '/([^/]+)')}$`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { A_TYPES__Component_Constructor, A_TYPES__Entity_Constructor, A_TYPES__Entity_Init, A_TYPES__Entity_Serialized } from "@adaas/a-concept"
|
|
2
|
+
import { A_Signal } from "./entities/A-Signal.entity"
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export type A_SignalConfig_Init = {
|
|
6
|
+
/**
|
|
7
|
+
* An array defining the structure of the signal vector.
|
|
8
|
+
*
|
|
9
|
+
* Each entry corresponds to a signal component constructor.
|
|
10
|
+
*/
|
|
11
|
+
structure?: Array<A_TYPES__Entity_Constructor<A_Signal>>
|
|
12
|
+
/**
|
|
13
|
+
* A string representation of the structure for easier DI resolution.
|
|
14
|
+
* Each signal's constructor name is used to form this string.
|
|
15
|
+
* e.g. "['A_RouterWatcher', 'A_ScopeWatcher', 'A_LoggerWatcher']"
|
|
16
|
+
* OR "A_RouterWatcher,A_ScopeWatcher,A_LoggerWatcher"
|
|
17
|
+
*/
|
|
18
|
+
stringStructure?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export type A_SignalVector_Init<
|
|
24
|
+
TSignalData extends Record<string, any> = Record<string, any>
|
|
25
|
+
> = {
|
|
26
|
+
structure: Array<A_TYPES__Entity_Constructor<A_Signal>>,
|
|
27
|
+
values: TSignalData[]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export type A_SignalVector_Serialized = A_TYPES__Entity_Serialized & {
|
|
32
|
+
structure: string[],
|
|
33
|
+
values: Array<Record<string, any>>
|
|
34
|
+
} & A_TYPES__Entity_Serialized
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export type A_Signal_Init<T extends Record<string, any> = Record<string, any>> = {
|
|
39
|
+
/**
|
|
40
|
+
* The signal name
|
|
41
|
+
*/
|
|
42
|
+
name?: string,
|
|
43
|
+
/**
|
|
44
|
+
* The signal data
|
|
45
|
+
*/
|
|
46
|
+
data: T
|
|
47
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { A_Caller, A_Component, A_Context, A_Feature, A_Inject, A_Scope } from "@adaas/a-concept";
|
|
2
|
+
import { A_SignalBusFeatures, A_SignalFeatures } from "../A-Signal.constants";
|
|
3
|
+
import { A_SignalState } from "../context/A-SignalState.context";
|
|
4
|
+
import { A_SignalConfig } from "../context/A-SignalConfig.context";
|
|
5
|
+
import { A_Config } from "../../A-Config/A-Config.context";
|
|
6
|
+
import { A_Logger } from "../../A-Logger/A-Logger.component";
|
|
7
|
+
import { A_Signal } from "../entities/A-Signal.entity";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This component should listen for all available signal watchers components in this and all parent scopes.
|
|
13
|
+
* When a signal is emitted, it should forward the signal to all registered watchers.
|
|
14
|
+
*
|
|
15
|
+
* A_SignalBus should always return the same vector structure of the signals, and that's why it should store the state of the latest behavior.
|
|
16
|
+
* For example if there are 3 watchers registered, the bus should always return a vector of 3 elements, based on the A_SignalConfig structure.
|
|
17
|
+
*
|
|
18
|
+
*
|
|
19
|
+
* The component itself is stateless and all methods uses only parameters (context) is provided with.
|
|
20
|
+
*/
|
|
21
|
+
export class A_SignalBus extends A_Component {
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This methods extends A-Signal Emit feature to handle signal emission within the bus.
|
|
26
|
+
*
|
|
27
|
+
* It updates the signal state and emits the updated signal vector.
|
|
28
|
+
*
|
|
29
|
+
* @param signal
|
|
30
|
+
* @param globalConfig
|
|
31
|
+
* @param logger
|
|
32
|
+
* @param state
|
|
33
|
+
* @param config
|
|
34
|
+
* @returns
|
|
35
|
+
*/u
|
|
36
|
+
@A_Feature.Extend({
|
|
37
|
+
scope: [A_Signal]
|
|
38
|
+
})
|
|
39
|
+
async [A_SignalFeatures.Emit](
|
|
40
|
+
@A_Inject(A_Caller) signal: A_Signal,
|
|
41
|
+
|
|
42
|
+
@A_Inject(A_Config) globalConfig?: A_Config<['A_SIGNAL_VECTOR_STRUCTURE']>,
|
|
43
|
+
@A_Inject(A_Logger) logger?: A_Logger,
|
|
44
|
+
@A_Inject(A_SignalState) state?: A_SignalState,
|
|
45
|
+
@A_Inject(A_SignalConfig) config?: A_SignalConfig,
|
|
46
|
+
) {
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* We need a context where component is registered, to prevent any duplicate registrations
|
|
50
|
+
*/
|
|
51
|
+
const componentContext = A_Context.scope(this)
|
|
52
|
+
|
|
53
|
+
if (!config) {
|
|
54
|
+
config = new A_SignalConfig({
|
|
55
|
+
stringStructure: globalConfig?.get('A_SIGNAL_VECTOR_STRUCTURE') || undefined
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
componentContext.register(config);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!config.ready)
|
|
62
|
+
await config.initialize();
|
|
63
|
+
|
|
64
|
+
if (!state) {
|
|
65
|
+
state = new A_SignalState(config.structure);
|
|
66
|
+
componentContext.register(state);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!state.has(signal))
|
|
70
|
+
return;
|
|
71
|
+
|
|
72
|
+
// ------------------------------------------------------------------
|
|
73
|
+
// And finally if all checks are passed, we can update the state
|
|
74
|
+
// ------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
logger?.debug(`A_SignalBus: Updating state for signal '${signal.constructor.name}' with data:`, signal.data);
|
|
77
|
+
|
|
78
|
+
state.set(signal, signal.data);
|
|
79
|
+
|
|
80
|
+
const vector = state.toVector();
|
|
81
|
+
|
|
82
|
+
const nextScope = new A_Scope({
|
|
83
|
+
name: `A_SignalBus_Next_Scope_of_${this.constructor.name}`,
|
|
84
|
+
entities: [vector]
|
|
85
|
+
})
|
|
86
|
+
.inherit(A_Context.scope(this));
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
|
|
91
|
+
await this.call(A_SignalBusFeatures.Emit, nextScope);
|
|
92
|
+
|
|
93
|
+
nextScope.destroy();
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
|
|
97
|
+
nextScope.destroy();
|
|
98
|
+
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { A_Context, A_Fragment, A_TYPES__Component_Constructor, A_TYPES__Entity_Constructor } from "@adaas/a-concept";
|
|
2
|
+
import { A_SignalConfig_Init } from "../A-Signal.types";
|
|
3
|
+
import { A_Signal } from "../entities/A-Signal.entity";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This component should dictate a structure of the vector for all signals within a given scope.
|
|
9
|
+
* so if there're multiple signals it should say what type at what position should be expected.
|
|
10
|
+
*
|
|
11
|
+
* e.g. [A_RouterWatcher, A_ScopeWatcher, A_LoggerWatcher]
|
|
12
|
+
* This structure then should be used for any further processing of signals within the scope.
|
|
13
|
+
*/
|
|
14
|
+
export class A_SignalConfig extends A_Fragment {
|
|
15
|
+
|
|
16
|
+
protected _structure?: Array<A_TYPES__Entity_Constructor<A_Signal>>;
|
|
17
|
+
|
|
18
|
+
protected _config: A_SignalConfig_Init
|
|
19
|
+
|
|
20
|
+
protected _ready?: Promise<void>;
|
|
21
|
+
|
|
22
|
+
get structure(): Array<A_TYPES__Entity_Constructor<A_Signal>> {
|
|
23
|
+
if (this._structure) {
|
|
24
|
+
return this._structure;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const scope = A_Context.scope(this);
|
|
28
|
+
const signalConfigs = scope.allowedEntities;
|
|
29
|
+
|
|
30
|
+
// just sort by constructor name to have consistent order
|
|
31
|
+
return [...scope.allowedEntities]
|
|
32
|
+
.sort((a, b) => a.constructor.name.localeCompare(b.constructor.name))
|
|
33
|
+
.map(s => scope.resolveConstructor<A_Signal>(s.constructor.name));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Uses for synchronization to ensure the config is initialized.
|
|
38
|
+
*
|
|
39
|
+
* @returns True if the configuration has been initialized.
|
|
40
|
+
*/
|
|
41
|
+
get ready() {
|
|
42
|
+
return this._ready;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
params: A_SignalConfig_Init
|
|
47
|
+
) {
|
|
48
|
+
super({ name: "A_SignalConfig" });
|
|
49
|
+
this._config = params;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initializes the signal configuration if not already initialized.
|
|
55
|
+
*
|
|
56
|
+
* @returns
|
|
57
|
+
*/
|
|
58
|
+
async initialize() {
|
|
59
|
+
if (!this._ready) {
|
|
60
|
+
this._ready = this._initialize();
|
|
61
|
+
}
|
|
62
|
+
return this._ready;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initializes the signal configuration by processing the provided structure or string representation.
|
|
67
|
+
* This method sets up the internal structure of signal constructors based on the configuration.
|
|
68
|
+
*/
|
|
69
|
+
protected async _initialize() {
|
|
70
|
+
if (this._config.structure) {
|
|
71
|
+
this._structure = this._config.structure;
|
|
72
|
+
} else if (this._config.stringStructure) {
|
|
73
|
+
const stringStructure = this._config.stringStructure.split(',').map(s => s.trim());
|
|
74
|
+
this._structure = stringStructure
|
|
75
|
+
.map(name => A_Context.scope(this).resolveConstructor<A_Signal>(name))
|
|
76
|
+
.filter(s => s);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|