@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.
@@ -16,9 +16,12 @@ The A_Logger component provides comprehensive logging capabilities with:
16
16
  ## Features
17
17
 
18
18
  ### 🎨 Visual Output
19
- - **Terminal Colors**: Support for 9 different colors (green, blue, red, yellow, gray, magenta, cyan, white, pink)
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.log('Application started');
60
- logger.log('green', 'Operation successful');
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.log('blue', 'User data:', user);
91
+ logger.info('deepBlue', 'User data:', user);
79
92
 
80
93
  // Multi-argument logging
81
- logger.log('green',
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.log('green', 'Creating user:', userData);
140
+ this.logger.info('brightBlue', 'Creating user:', userData);
128
141
 
129
142
  try {
130
143
  // User creation logic
131
- this.logger.log('User created successfully');
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.log('green', 'Service initialized');
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.log('green', 'Operation successful'); // Success
322
- logger.log('blue', 'Processing data...'); // Info
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.log('blue', 'User operation:', {
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.log('green',
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
+
@@ -0,0 +1,10 @@
1
+
2
+ export enum A_SignalFeatures {
3
+ Emit = '_A_SignalFeatures_Emit',
4
+ }
5
+
6
+
7
+
8
+ export enum A_SignalBusFeatures {
9
+ Emit = '_A_SignalBusFeatures_Emit',
10
+ }
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
+ }