@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.
@@ -0,0 +1,197 @@
1
+ import { A_Context, A_Fragment, A_TYPES__Component_Constructor } from "@adaas/a-concept";
2
+ import { A_Signal } from "../entities/A-Signal.entity";
3
+ import { A_SignalVector } from "../entities/A-SignalVector.entity";
4
+
5
+ /**
6
+ * A_SignalState manages the latest state of all signals within a given scope.
7
+ *
8
+ * This class maintains a mapping between signal constructors and their most recently emitted values,
9
+ * providing a centralized state store for signal management within an application context.
10
+ *
11
+ * @template TSignalData - Union type of all possible signal data types that can be stored (must extend Record<string, any>)
12
+ *
13
+ * The generic ensures type safety by maintaining correspondence between:
14
+ * - Signal constructor types and their data types
15
+ * - Signal instances and their emitted value types
16
+ * - Vector structure and the data it contains
17
+ */
18
+ export class A_SignalState<
19
+ TSignalData extends Record<string, any> = Record<string, any>
20
+ > extends A_Fragment {
21
+
22
+ /**
23
+ * Internal map storing the relationship between signal constructors and their latest values
24
+ * Key: Signal constructor function
25
+ * Value: Latest emitted data from that signal type
26
+ */
27
+ protected _state: Map<A_TYPES__Component_Constructor<A_Signal>, TSignalData | undefined> = new Map();
28
+
29
+ /**
30
+ * Optional structure defining the ordered list of signal constructors
31
+ * Used for vector operations and initialization
32
+ */
33
+ protected _structure: Array<A_TYPES__Component_Constructor<A_Signal>>;
34
+
35
+
36
+ /**
37
+ * Gets the ordered structure of signal constructors
38
+ * @returns Array of signal constructors in their defined order
39
+ */
40
+ get structure(): Array<A_TYPES__Component_Constructor<A_Signal>> {
41
+ return this._structure || [];
42
+ }
43
+
44
+ /**
45
+ * Creates a new A_SignalState instance
46
+ *
47
+ * @param structure - Optional array defining the ordered structure of signal constructors
48
+ * This structure is used for vector operations and determines the order
49
+ * in which signals are processed and serialized
50
+ */
51
+ constructor(
52
+ structure: A_TYPES__Component_Constructor<A_Signal>[]
53
+ ) {
54
+ super({ name: "A_SignalState" });
55
+
56
+ this._structure = structure;
57
+
58
+ // Initialize the state map with undefined values for each signal in the structure
59
+ // This ensures all expected signals have entries in the state map from the start
60
+ this.structure.forEach((signalConstructor) => {
61
+ this._state.set(signalConstructor, undefined);
62
+ });
63
+ }
64
+
65
+
66
+ /**
67
+ * Sets the latest value for a specific signal type
68
+ *
69
+ * @param signal - The signal constructor to associate the value with
70
+ * @param value - The data value emitted by the signal
71
+ */
72
+ set(
73
+ signal: A_Signal,
74
+ value: TSignalData
75
+ ): void
76
+ set(
77
+ signal: A_TYPES__Component_Constructor<A_Signal>,
78
+ value: TSignalData
79
+ ): void
80
+ set(
81
+ param1: A_TYPES__Component_Constructor<A_Signal> | A_Signal,
82
+ param2: TSignalData
83
+ ): void {
84
+ const signal = param1 instanceof A_Signal ? param1.constructor as A_TYPES__Component_Constructor<A_Signal> : param1;
85
+ const value = param2;
86
+
87
+ this._state.set(signal, value);
88
+ }
89
+
90
+ /**
91
+ * Retrieves the latest value for a specific signal type
92
+ *
93
+ * @param signal - The signal constructor to get the value for
94
+ * @returns The latest data value or undefined if no value has been set
95
+ */
96
+ get(
97
+ signal: A_Signal
98
+ ): TSignalData | undefined
99
+ get(
100
+ signal: A_TYPES__Component_Constructor<A_Signal>
101
+ ): TSignalData | undefined
102
+ get(
103
+ param: A_TYPES__Component_Constructor<A_Signal> | A_Signal
104
+ ): TSignalData | undefined {
105
+ const signal = param instanceof A_Signal ? param.constructor as A_TYPES__Component_Constructor<A_Signal> : param;
106
+ return this._state.get(signal);
107
+ }
108
+
109
+ /**
110
+ * Checks if a signal type has been registered in the state
111
+ *
112
+ * @param signal - The signal constructor to check for
113
+ * @returns True if the signal type exists in the state map
114
+ */
115
+ has(
116
+ signal: A_Signal
117
+ ): boolean
118
+ has(
119
+ signal: A_TYPES__Component_Constructor<A_Signal>
120
+ ): boolean
121
+ has(
122
+ param: A_TYPES__Component_Constructor<A_Signal> | A_Signal
123
+ ): boolean {
124
+ const signal = param instanceof A_Signal ? param.constructor as A_TYPES__Component_Constructor<A_Signal> : param;
125
+ return this.structure.includes(signal);
126
+ }
127
+
128
+ /**
129
+ * Removes a signal type and its associated value from the state
130
+ *
131
+ * @param signal - The signal constructor to remove
132
+ * @returns True if the signal was successfully deleted, false if it didn't exist
133
+ */
134
+ delete(
135
+ signal: A_Signal
136
+ ): boolean
137
+ delete(
138
+ signal: A_TYPES__Component_Constructor<A_Signal>
139
+ ): boolean
140
+ delete(
141
+ param: A_TYPES__Component_Constructor<A_Signal> | A_Signal
142
+ ): boolean {
143
+ const signal = param instanceof A_Signal ? param.constructor as A_TYPES__Component_Constructor<A_Signal> : param;
144
+ return this._state.delete(signal);
145
+ }
146
+
147
+
148
+ /**
149
+ * Converts the current state to a vector (ordered array) format
150
+ *
151
+ * The order is determined by the structure array provided during construction.
152
+ * Each position in the vector corresponds to a specific signal type's latest value.
153
+ *
154
+ * @returns Array of signal values in the order defined by the structure
155
+ * @throws Error if structure is not defined or if any signal value is undefined
156
+ */
157
+ toVector(): A_SignalVector {
158
+ const vector: TSignalData[] = [];
159
+
160
+ this.structure.forEach((signalConstructor) => {
161
+ const value = this._state.get(signalConstructor);
162
+ if (value === undefined) {
163
+ throw new Error(`Signal ${signalConstructor.name} has no value in state`);
164
+ }
165
+ vector.push(value);
166
+ });
167
+
168
+ return new A_SignalVector({
169
+ structure: this.structure,
170
+ values: vector
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Converts the current state to an object with signal constructor names as keys
176
+ *
177
+ * This provides a more readable representation of the state where each signal
178
+ * type is identified by its constructor name.
179
+ *
180
+ * @returns Object mapping signal constructor names to their latest values
181
+ * @throws Error if any signal value is undefined
182
+ */
183
+ toObject(): Record<string, TSignalData> {
184
+ const obj: Record<string, TSignalData> = {};
185
+
186
+ this.structure.forEach((signalConstructor) => {
187
+ const value = this._state.get(signalConstructor);
188
+ if (value === undefined) {
189
+ throw new Error(`Signal ${signalConstructor.name} has no value in state`);
190
+ }
191
+ obj[signalConstructor.name] = value;
192
+ });
193
+
194
+ return obj;
195
+ }
196
+
197
+ }
@@ -0,0 +1,42 @@
1
+ import { A_Entity, A_Scope } from "@adaas/a-concept";
2
+ import { A_Signal_Init } from "../A-Signal.types";
3
+ import { A_SignalFeatures } from "../A-Signal.constants";
4
+
5
+ /**
6
+ * A Signal Entity is an individual signal instance that carries data.
7
+ * Signals is a event types that uses for vectors of signals to be used for further processing.
8
+ *
9
+ * Comparing to standard events, signals should be used in case when the event impacts some "state"
10
+ * and the state should be used instead of the event itself.
11
+ *
12
+ * For example, a signal can represent the current status of a user (online/offline/away),
13
+ * while an event would represent a single action (user logged in/logged out).
14
+ *
15
+ * Signals are typically used in scenarios where the current state is more important than individual events,
16
+ * such as monitoring systems, real-time dashboards, or stateful applications.
17
+ */
18
+ export class A_Signal extends A_Entity<A_Signal_Init> {
19
+
20
+ data!: Record<string, any>;
21
+
22
+
23
+
24
+
25
+ fromNew(newEntity: A_Signal_Init): void {
26
+ this.aseid = this.generateASEID({ entity: newEntity.name });
27
+ this.data = newEntity.data;
28
+ }
29
+
30
+ /**
31
+ * Emits this signal within the provided scope.
32
+ *
33
+ * Scope is mandatory since signal itself should not be registered in the scope,
34
+ * but should use particular scope context to use proper set of components
35
+ *
36
+ * @param scope
37
+ */
38
+ async emit(scope: A_Scope) {
39
+ await this.call(A_SignalFeatures.Emit, scope);
40
+ }
41
+
42
+ }
@@ -0,0 +1,83 @@
1
+ import { A_Entity, A_TYPES__Component_Constructor, A_TYPES__Entity_Constructor } from "@adaas/a-concept";
2
+ import { A_SignalVector_Serialized, A_SignalVector_Init } from "../A-Signal.types";
3
+ import { A_Signal } from "./A-Signal.entity";
4
+
5
+
6
+ /**
7
+ * A Signal Vector Entity is a collection of signals structured in a specific way.
8
+ * It allows grouping multiple signals together for batch processing or transmission.
9
+ *
10
+ * Signal Vectors are useful in scenarios where multiple related signals need to be handled together,
11
+ * as a state of the system or a snapshot of various parameters at a given time.
12
+ */
13
+ export class A_SignalVector extends A_Entity<A_SignalVector_Init, A_SignalVector_Serialized> {
14
+
15
+ /**
16
+ * The structure of the signal vector, defining the types of signals it contains.
17
+ *
18
+ * For example:
19
+ * [UserSignInSignal, UserStatusSignal, UserActivitySignal]
20
+ */
21
+ structure!: Array<A_TYPES__Entity_Constructor<A_Signal>>;
22
+ /**
23
+ * The values of the signals in the vector.
24
+ * Each entry corresponds to the data of a signal in the structure.
25
+ *
26
+ * For example:
27
+ * [
28
+ * { userId: '123', timestamp: '2023-10-01T12:00:00Z' }, // UserSignInSignal data
29
+ * { userId: '123', status: 'online' }, // UserStatusSignal data
30
+ * { userId: '123', activity: 'browsing' } // UserActivitySignal data
31
+ * ]
32
+ *
33
+ *
34
+ * [!] For further processing it's recommended to convert any objects to plain text
35
+ */
36
+ values!: Array<Record<string, any>>;
37
+
38
+
39
+ fromNew(newEntity: A_SignalVector_Init): void {
40
+ super.fromNew(newEntity);
41
+ this.structure = newEntity.structure;
42
+ this.values = newEntity.values;
43
+ }
44
+
45
+
46
+ /**
47
+ * Converts to Array of values of signals in the vector
48
+ *
49
+ * @returns
50
+ */
51
+ toVector(): Array<Record<string, any>> {
52
+ return this.values;
53
+ }
54
+
55
+ /**
56
+ * Converts to Object with signal names as keys and their corresponding values
57
+ *
58
+ * @returns
59
+ */
60
+ toObject(): Record<string, any> {
61
+ const obj: Record<string, any> = {};
62
+ this.structure.forEach((signalConstructor, index) => {
63
+ const signalName = signalConstructor.name;
64
+ obj[signalName] = this.values[index];
65
+ });
66
+ return obj;
67
+ }
68
+
69
+
70
+ /**
71
+ * Serializes the Signal Vector to a JSON-compatible format.
72
+ *
73
+ *
74
+ * @returns
75
+ */
76
+ toJSON(): A_SignalVector_Serialized {
77
+ return {
78
+ ...super.toJSON(),
79
+ structure: this.structure.map(s => s.name),
80
+ values: this.values
81
+ };
82
+ }
83
+ }
@@ -215,7 +215,8 @@ describe('A_Logger Component', () => {
215
215
 
216
216
  const logs = getCapturedLogs();
217
217
  expect(logs[0]).toContain('Test error message');
218
- expect(logs[0]).toContain('"name": "Error"');
218
+ expect(logs[0]).toContain('ERROR: Error');
219
+ expect(logs[0]).toContain('STACK TRACE:');
219
220
  });
220
221
 
221
222
  test('should format A_Error instances with special formatting', () => {
@@ -253,7 +254,7 @@ describe('A_Logger Component', () => {
253
254
 
254
255
  colors.forEach(color => {
255
256
  clearCapturedLogs();
256
- logger.log(color, `${color} message`);
257
+ logger.info(color, `${color} message`);
257
258
 
258
259
  const logs = getCapturedLogs();
259
260
  expect(logs[0]).toContain(`${color} message`);
@@ -261,6 +262,26 @@ describe('A_Logger Component', () => {
261
262
  });
262
263
  });
263
264
 
265
+ test('should support extended color palette from A_LoggerColorName enum', () => {
266
+ const extendedColors: Array<keyof typeof A_LOGGER_COLORS> = [
267
+ 'brightBlue', 'brightCyan', 'brightMagenta',
268
+ 'indigo', 'violet', 'purple', 'lavender',
269
+ 'skyBlue', 'steelBlue', 'slateBlue', 'deepBlue',
270
+ 'lightBlue', 'periwinkle', 'cornflower', 'powder',
271
+ 'darkGray', 'lightGray', 'charcoal', 'silver', 'smoke', 'slate'
272
+ ];
273
+
274
+ extendedColors.forEach(color => {
275
+ clearCapturedLogs();
276
+ logger.info(color, `Extended ${color} message`);
277
+
278
+ const logs = getCapturedLogs();
279
+ // Clean the output to handle line wrapping
280
+ const cleanedOutput = logs[0].replace(/\n\s*\|\s*/g, ' ').replace(/\s+/g, ' ');
281
+ expect(cleanedOutput).toContain(`Extended ${color} message`);
282
+ });
283
+ });
284
+
264
285
  test('should default to blue color when no color specified', () => {
265
286
  logger.log('Default color message');
266
287
 
@@ -409,13 +430,19 @@ describe('A_Logger Component', () => {
409
430
  expect(logs[0]).toContain('[Circular Reference]');
410
431
  });
411
432
 
412
- test('should handle very long strings', () => {
433
+ test('should handle very long strings with wrapping', () => {
413
434
  const longString = 'A'.repeat(1000);
414
435
 
415
- logger.log('Long string:', longString);
436
+ logger.info('Long string:', longString);
416
437
 
417
438
  const logs = getCapturedLogs();
418
- expect(logs[0]).toContain(longString);
439
+ // The logger now wraps long strings, so we check for the presence of parts of the string
440
+ // rather than the entire string in one line
441
+ expect(logs[0]).toContain('Long string:');
442
+ expect(logs[0]).toContain('AAAAAAAAAAA'); // Should contain many A's
443
+ // The string should be wrapped across multiple lines
444
+ const logLines = logs[0].split('\n');
445
+ expect(logLines.length).toBeGreaterThan(5); // Should be wrapped into multiple lines
419
446
  });
420
447
 
421
448
  test('should handle empty scope name', () => {
@@ -466,6 +493,64 @@ describe('A_Logger Component', () => {
466
493
  expect(endTime - startTime).toBeLessThan(500); // Should complete within 500ms
467
494
  });
468
495
  });
496
+
497
+ // =============================================
498
+ // Terminal Width and Formatting Tests
499
+ // =============================================
500
+
501
+ describe('Terminal Width Detection and Formatting', () => {
502
+ test('should detect terminal width in Node.js environment', () => {
503
+ // The logger should initialize without errors and handle terminal width detection
504
+ expect(() => {
505
+ const testScope = new A_Scope({ name: 'TerminalTest' });
506
+ const terminalLogger = new A_Logger(testScope);
507
+ terminalLogger.info('Terminal width test message');
508
+ }).not.toThrow();
509
+ });
510
+
511
+ test('should wrap long messages appropriately', () => {
512
+ // Create a message that's extremely long to ensure wrapping even on wide terminals
513
+ // The message needs to be longer than most reasonable terminal widths (200+ chars)
514
+ const extremelyLongMessage = 'This is an extraordinarily and exceptionally long message designed specifically for testing text wrapping functionality in the A_Logger component, containing numerous words and phrases that together form a sentence of considerable length that should definitely exceed the available width in most terminal configurations, thereby demonstrating the logger\'s sophisticated text wrapping capabilities and ensuring proper formatting across diverse environments with varying screen sizes and terminal window dimensions, while maintaining readability and professional presentation standards.';
515
+
516
+ logger.info('cyan', extremelyLongMessage);
517
+
518
+ const logs = getCapturedLogs();
519
+
520
+ // Remove all newlines and extra spaces to get the actual content for verification
521
+ const cleanedOutput = logs[0].replace(/\n\s*\|\s*/g, ' ').replace(/\s+/g, ' ');
522
+
523
+ // Verify the message content is preserved regardless of wrapping
524
+ expect(cleanedOutput).toContain('extraordinarily and exceptionally long message');
525
+ expect(cleanedOutput).toContain('readability and professional presentation');
526
+
527
+ // The message should either be wrapped with newlines OR be very long on one line
528
+ const logOutput = logs[0];
529
+ const hasWrapping = logOutput.includes('\n') && logOutput.includes('|'); // Continuation markers
530
+ const isSingleLongLine = logOutput.length > 300; // Very long single line
531
+
532
+ // In narrow terminals, wrapping occurs with continuation markers
533
+ // In wide terminals, the message appears as one very long line
534
+ expect(hasWrapping || isSingleLongLine).toBe(true);
535
+
536
+ // Ensure the complete message is present (checking cleaned version)
537
+ expect(cleanedOutput).toContain('extraordinarily and exceptionally long message');
538
+ expect(cleanedOutput).toContain('professional presentation standards');
539
+ });
540
+
541
+ test('should handle multi-argument messages with proper indentation', () => {
542
+ logger.info('brightMagenta',
543
+ 'First long argument that might wrap across lines',
544
+ 'Second argument for testing indentation',
545
+ { complexObject: 'with nested data for formatting tests' }
546
+ );
547
+
548
+ const logs = getCapturedLogs();
549
+ expect(logs[0]).toContain('First long argument');
550
+ expect(logs[0]).toContain('Second argument');
551
+ expect(logs[0]).toContain('complexObject');
552
+ });
553
+ });
469
554
  });
470
555
 
471
556
  // =============================================
@@ -0,0 +1,34 @@
1
+ import { A_Route } from "@adaas/a-utils/lib/A-Route/A-Route.entity";
2
+
3
+
4
+
5
+
6
+ jest.retryTimes(0);
7
+
8
+ describe('A-Route tests', () => {
9
+ it('Should Allow to create a new A-Route', async () => {
10
+ let route = new A_Route('/test/route');
11
+
12
+ expect(route).toBeInstanceOf(A_Route);
13
+ expect(route.path).toBe('/test/route');
14
+ });
15
+ it('Should properly parse and extract path params', async () => {
16
+ let route = new A_Route('/test/route/:param1/:param2');
17
+
18
+ expect(route).toBeInstanceOf(A_Route);
19
+ expect(route.path).toBe('/test/route/:param1/:param2');
20
+ expect(route.params).toEqual(['param1', 'param2']);
21
+
22
+ const extractedParams = route.extractParams('/test/route/value1/value2');
23
+ expect(extractedParams).toEqual({ param1: 'value1', param2: 'value2' });
24
+ });
25
+ it('Should properly parse received URL', async () => {
26
+ let route = new A_Route('https://example.com/test/route?param1=value1&param2=value2');
27
+
28
+ expect(route).toBeInstanceOf(A_Route);
29
+ expect(route.path).toBe('/test/route');
30
+ const query = route.extractQuery('https://example.com/test/route?param1=value1&param2=value2');
31
+ expect(query).toEqual({ param1: 'value1', param2: 'value2' });
32
+ });
33
+
34
+ })
@@ -0,0 +1,66 @@
1
+ import { A_Component, A_Concept, A_Container, A_Context, A_Feature, A_Inject, A_Scope } from "@adaas/a-concept";
2
+ import { A_SignalBusFeatures, A_SignalFeatures } from "@adaas/a-utils/lib/A-Signal/A-Signal.constants";
3
+ import { A_SignalBus } from "@adaas/a-utils/lib/A-Signal/components/A-SignalBus.component";
4
+ import { A_SignalConfig } from "@adaas/a-utils/lib/A-Signal/context/A-SignalConfig.context";
5
+ import { A_Signal } from "@adaas/a-utils/lib/A-Signal/entities/A-Signal.entity";
6
+ import { A_SignalVector } from "@adaas/a-utils/lib/A-Signal/entities/A-SignalVector.entity";
7
+
8
+
9
+
10
+ jest.retryTimes(0);
11
+
12
+ describe('A-Signal tests', () => {
13
+ it('Should Allow to create a config object', async () => {
14
+
15
+ let result;
16
+
17
+ class UserIntentionListener extends A_Component {
18
+ @A_Concept.Start()
19
+ async start(
20
+ @A_Inject(A_Scope) scope: A_Scope,
21
+ @A_Inject(A_SignalBus) bus: A_SignalBus
22
+ ) {
23
+
24
+
25
+ const signal = new A_Signal({
26
+ data: {
27
+ buttonId: 'submit-order'
28
+ }
29
+ })
30
+
31
+ console.log(A_Context.featureTemplate(A_SignalFeatures.Emit, signal, scope))
32
+
33
+ await signal.emit(scope)
34
+ }
35
+
36
+
37
+ @A_Feature.Extend()
38
+ async [A_SignalBusFeatures.Emit](
39
+ @A_Inject(A_SignalVector) vector: A_SignalVector
40
+ ) {
41
+ result = vector;
42
+ }
43
+ }
44
+
45
+ const concept = new A_Concept({
46
+ name: 'test-concept',
47
+ containers: [new A_Container({
48
+ name: 'test-container',
49
+ components: [A_SignalBus, UserIntentionListener],
50
+ fragments: [
51
+ new A_SignalConfig({
52
+ structure: [A_Signal]
53
+ })
54
+ ]
55
+ })]
56
+ });
57
+
58
+ await concept.load();
59
+ await concept.start();
60
+
61
+ expect(result).toBeDefined();
62
+ expect(result.values.length).toBe(1);
63
+ expect(result.values[0].buttonId).toBe('submit-order');
64
+
65
+ });
66
+ })
@@ -1,33 +0,0 @@
1
- import { A_Concept, A_Container } from "@adaas/a-concept"
2
- import { A_ConfigLoader } from "@adaas/a-utils/lib/A-Config/A-Config.container";
3
- import { A_Config } from "@adaas/a-utils/lib/A-Config/A-Config.context";
4
- import { ENVConfigReader } from "@adaas/a-utils/lib/A-Config/components/ENVConfigReader.component";
5
- import { A_Polyfill } from "@adaas/a-utils/lib/A-Polyfill/A-Polyfill.component";
6
-
7
-
8
-
9
- (async () => {
10
-
11
-
12
- const service1 = new A_ConfigLoader({
13
- name: 'ConfigLoaderContainer',
14
- components: [
15
- A_Polyfill, ENVConfigReader
16
- ]
17
- })
18
-
19
- const concept = new A_Concept({
20
- name: 'test-config',
21
- containers: [service1],
22
- });
23
-
24
-
25
- await concept.load();
26
- await concept.start();
27
-
28
-
29
- const config = service1.scope.resolve<A_Config<['ADAAS_SSO_LOCATION']>>(A_Config)!;
30
-
31
- console.log('config: ', config.get('ADAAS_SSO_LOCATION'))
32
-
33
- })()