@adaas/a-utils 0.1.11 → 0.1.13

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,7 +16,7 @@ describe('A-Command tests', () => {
16
16
  expect(command.code).toBe('a-command');
17
17
  expect(command.id).toBeDefined();
18
18
  expect(command.aseid).toBeDefined();
19
- expect(command.status).toBe(A_CONSTANTS__A_Command_Status.INITIALIZED);
19
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
20
20
  expect(command.scope).toBeInstanceOf(A_Scope);
21
21
  expect(command.scope.resolve(A_Memory)).toBeInstanceOf(A_Memory);
22
22
  });
@@ -30,6 +30,30 @@ describe('A-Command tests', () => {
30
30
  expect(command.startedAt).toBeInstanceOf(Date);
31
31
  expect(command.endedAt).toBeInstanceOf(Date);
32
32
  });
33
+ it('Should Allow to create a command with custom generic types', async () => {
34
+ type LifecycleEvents = 'A_CUSTOM_EVENT_1' | 'A_CUSTOM_EVENT_2';
35
+
36
+ class MyCommand extends A_Command<
37
+ { foo: string },
38
+ { bar: string },
39
+ LifecycleEvents
40
+ > { }
41
+
42
+ const command = new MyCommand({ foo: 'baz' });
43
+
44
+ A_Context.root.register(command);
45
+
46
+ command.emit('A_CUSTOM_EVENT_1');
47
+ command.emit('compile');
48
+
49
+ expect(command).toBeInstanceOf(A_Command);
50
+ expect(command.code).toBe('my-command');
51
+ expect(command.id).toBeDefined();
52
+ expect(command.aseid).toBeDefined();
53
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
54
+ expect(command.scope).toBeInstanceOf(A_Scope);
55
+ expect(command.scope.resolve(A_Memory)).toBeInstanceOf(A_Memory);
56
+ });
33
57
  it('Should allow to serialize and deserialize a command', async () => {
34
58
  const command = new A_Command({});
35
59
  A_Context.root.register(command);
@@ -53,7 +77,7 @@ describe('A-Command tests', () => {
53
77
  expect(deserializedCommand.startedAt?.toISOString()).toBe(command.startedAt?.toISOString());
54
78
  expect(deserializedCommand.duration).toBe(command.duration);
55
79
  });
56
-
80
+
57
81
  it('Should allow to execute a command with custom logic', async () => {
58
82
 
59
83
  // 1) create a scope
@@ -131,4 +155,425 @@ describe('A-Command tests', () => {
131
155
  expect(command.errors?.size).toBe(1);
132
156
  expect(Array.from(command.errors?.values() || [])[0].message).toBe('Test error');
133
157
  });
158
+
159
+ describe('Command Lifecycle Tests', () => {
160
+ beforeEach(() => {
161
+ A_Context.reset();
162
+ });
163
+
164
+ it('Should follow correct lifecycle sequence during execution', async () => {
165
+ const lifecycleOrder: string[] = [];
166
+
167
+ class TestCommand extends A_Command<{ input: string }, { output: string }> {}
168
+
169
+ const command = new TestCommand({ input: 'test' });
170
+ A_Context.root.register(command);
171
+
172
+ // Track lifecycle events
173
+ command.on('init', () => lifecycleOrder.push('init'));
174
+ command.on('compile', () => lifecycleOrder.push('compile'));
175
+ command.on('execute', () => lifecycleOrder.push('execute'));
176
+ command.on('complete', () => lifecycleOrder.push('complete'));
177
+ command.on('fail', () => lifecycleOrder.push('fail'));
178
+
179
+ await command.execute();
180
+
181
+ expect(lifecycleOrder).toEqual(['init', 'compile', 'execute', 'complete']);
182
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPLETED);
183
+ });
184
+
185
+ it('Should track status changes through lifecycle', async () => {
186
+ const statusChanges: A_CONSTANTS__A_Command_Status[] = [];
187
+
188
+ class TestCommand extends A_Command<{ input: string }, { output: string }> {}
189
+
190
+ const command = new TestCommand({ input: 'test' });
191
+ A_Context.root.register(command);
192
+
193
+ // Initial status
194
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.CREATED);
195
+ statusChanges.push(command.status);
196
+
197
+ await command.init();
198
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.INITIALIZED);
199
+ statusChanges.push(command.status);
200
+
201
+ await command.compile();
202
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPILED);
203
+ statusChanges.push(command.status);
204
+
205
+ await command.complete();
206
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.COMPLETED);
207
+ statusChanges.push(command.status);
208
+
209
+ expect(statusChanges).toEqual([
210
+ A_CONSTANTS__A_Command_Status.CREATED,
211
+ A_CONSTANTS__A_Command_Status.INITIALIZED,
212
+ A_CONSTANTS__A_Command_Status.COMPILED,
213
+ A_CONSTANTS__A_Command_Status.COMPLETED
214
+ ]);
215
+ });
216
+
217
+ it('Should handle failed lifecycle correctly', async () => {
218
+ A_Context.reset();
219
+
220
+ class FailingCommand extends A_Command<{ input: string }, { output: string }> {}
221
+
222
+ class FailingComponent extends A_Component {
223
+ @A_Feature.Extend({ scope: [FailingCommand] })
224
+ async [A_CONSTANTS_A_Command_Features.EXECUTE]() {
225
+ throw new A_Error({ title: 'Execution failed' });
226
+ }
227
+ }
228
+
229
+ A_Context.root.register(FailingComponent);
230
+
231
+ const command = new FailingCommand({ input: 'test' });
232
+ A_Context.root.register(command);
233
+
234
+ const lifecycleOrder: string[] = [];
235
+ command.on('init', () => lifecycleOrder.push('init'));
236
+ command.on('compile', () => lifecycleOrder.push('compile'));
237
+ command.on('execute', () => lifecycleOrder.push('execute'));
238
+ command.on('complete', () => lifecycleOrder.push('complete'));
239
+ command.on('fail', () => lifecycleOrder.push('fail'));
240
+
241
+ await command.execute();
242
+
243
+ expect(lifecycleOrder).toEqual(['init', 'compile', 'execute', 'fail']);
244
+ expect(command.status).toBe(A_CONSTANTS__A_Command_Status.FAILED);
245
+ expect(command.isFailed).toBe(true);
246
+ expect(command.isCompleted).toBe(false);
247
+ });
248
+
249
+ it('Should track execution timing correctly', async () => {
250
+ const command = new A_Command({});
251
+ A_Context.root.register(command);
252
+
253
+ expect(command.startedAt).toBeUndefined();
254
+ expect(command.endedAt).toBeUndefined();
255
+ expect(command.duration).toBeUndefined();
256
+
257
+ const startTime = Date.now();
258
+ await command.execute();
259
+ const endTime = Date.now();
260
+
261
+ expect(command.startedAt).toBeInstanceOf(Date);
262
+ expect(command.endedAt).toBeInstanceOf(Date);
263
+ expect(command.duration).toBeGreaterThanOrEqual(0);
264
+ expect(command.startedAt!.getTime()).toBeGreaterThanOrEqual(startTime);
265
+ expect(command.endedAt!.getTime()).toBeLessThanOrEqual(endTime);
266
+ expect(command.duration).toBe(command.endedAt!.getTime() - command.startedAt!.getTime());
267
+ });
268
+ });
269
+
270
+ describe('Command Subscribers/Event Listeners Tests', () => {
271
+ beforeEach(() => {
272
+ A_Context.reset();
273
+ });
274
+
275
+ it('Should allow multiple listeners for the same event', async () => {
276
+ const command = new A_Command({});
277
+ A_Context.root.register(command);
278
+
279
+ const listener1Calls: number[] = [];
280
+ const listener2Calls: number[] = [];
281
+
282
+ command.on('init', () => listener1Calls.push(1));
283
+ command.on('init', () => listener2Calls.push(2));
284
+
285
+ await command.init();
286
+
287
+ expect(listener1Calls).toEqual([1]);
288
+ expect(listener2Calls).toEqual([2]);
289
+ });
290
+
291
+ it('Should support custom lifecycle events', async () => {
292
+ type CustomEvents = 'custom-event-1' | 'custom-event-2';
293
+
294
+ class CustomCommand extends A_Command<{}, {}, CustomEvents> {}
295
+
296
+ const command = new CustomCommand({});
297
+ A_Context.root.register(command);
298
+
299
+ const customEvent1Calls: number[] = [];
300
+ const customEvent2Calls: number[] = [];
301
+
302
+ command.on('custom-event-1', () => customEvent1Calls.push(1));
303
+ command.on('custom-event-2', () => customEvent2Calls.push(2));
304
+
305
+ command.emit('custom-event-1');
306
+ command.emit('custom-event-2');
307
+ command.emit('custom-event-1');
308
+
309
+ expect(customEvent1Calls).toEqual([1, 1]);
310
+ expect(customEvent2Calls).toEqual([2]);
311
+ });
312
+
313
+ it('Should allow removing event listeners', async () => {
314
+ const command = new A_Command({});
315
+ A_Context.root.register(command);
316
+
317
+ const listenerCalls: number[] = [];
318
+ const listener = () => listenerCalls.push(1);
319
+
320
+ command.on('init', listener);
321
+ await command.init();
322
+ expect(listenerCalls).toEqual([1]);
323
+
324
+ // Remove listener and verify it's not called again
325
+ command.off('init', listener);
326
+
327
+ // Reset to call init again
328
+ (command as any)._status = A_CONSTANTS__A_Command_Status.CREATED;
329
+ await command.init();
330
+ expect(listenerCalls).toEqual([1]); // Should still be 1, not 2
331
+ });
332
+
333
+ it('Should pass command instance to event listeners', async () => {
334
+ const command = new A_Command({ testParam: 'value' });
335
+ A_Context.root.register(command);
336
+
337
+ let receivedCommand: A_Command<any, any, any> | undefined = undefined;
338
+
339
+ command.on('init', (cmd) => {
340
+ receivedCommand = cmd;
341
+ });
342
+
343
+ await command.init();
344
+
345
+ expect(receivedCommand).toBe(command);
346
+ expect((receivedCommand as any)?.params).toEqual({ testParam: 'value' });
347
+ });
348
+
349
+ it('Should propagate listener errors during event emission', async () => {
350
+ const command = new A_Command({});
351
+ A_Context.root.register(command);
352
+
353
+ const successfulCalls: number[] = [];
354
+
355
+ command.on('init', () => {
356
+ throw new Error('Listener error');
357
+ });
358
+ command.on('init', () => successfulCalls.push(1));
359
+
360
+ // Listener errors are propagated and will cause the command to fail
361
+ await expect(command.init()).rejects.toThrow('Listener error');
362
+ // The second listener may not be called due to the error
363
+ });
364
+ });
365
+
366
+ describe('Parameter Serialization and Transmission Tests', () => {
367
+ beforeEach(() => {
368
+ A_Context.reset();
369
+ });
370
+
371
+ it('Should preserve complex parameter types during serialization', async () => {
372
+ interface ComplexParams {
373
+ stringParam: string;
374
+ numberParam: number;
375
+ booleanParam: boolean;
376
+ objectParam: {
377
+ nested: string;
378
+ array: number[];
379
+ };
380
+ arrayParam: string[];
381
+ dateParam: string; // ISO string representation
382
+ nullParam: null;
383
+ undefinedParam?: undefined;
384
+ }
385
+
386
+ const complexParams: ComplexParams = {
387
+ stringParam: 'test string',
388
+ numberParam: 42,
389
+ booleanParam: true,
390
+ objectParam: {
391
+ nested: 'nested value',
392
+ array: [1, 2, 3]
393
+ },
394
+ arrayParam: ['a', 'b', 'c'],
395
+ dateParam: new Date('2023-01-01').toISOString(),
396
+ nullParam: null
397
+ };
398
+
399
+ class ComplexCommand extends A_Command<ComplexParams, { result: string }> {}
400
+
401
+ const command = new ComplexCommand(complexParams);
402
+ A_Context.root.register(command);
403
+
404
+ await command.execute();
405
+
406
+ const serialized = command.toJSON();
407
+ expect(serialized.params).toEqual(complexParams);
408
+
409
+ // Test deserialization
410
+ const deserializedCommand = new ComplexCommand(serialized);
411
+ expect(deserializedCommand.params).toEqual(complexParams);
412
+ expect(deserializedCommand.params.objectParam.nested).toBe('nested value');
413
+ expect(deserializedCommand.params.arrayParam).toEqual(['a', 'b', 'c']);
414
+ });
415
+
416
+ it('Should handle result serialization correctly', async () => {
417
+ A_Context.reset();
418
+
419
+ interface TestResult {
420
+ processedData: string;
421
+ count: number;
422
+ metadata: {
423
+ timestamp: string;
424
+ version: number;
425
+ };
426
+ }
427
+
428
+ class ResultCommand extends A_Command<{ input: string }, TestResult> {}
429
+
430
+ class ResultProcessor extends A_Component {
431
+ @A_Feature.Extend({ scope: [ResultCommand] })
432
+ async [A_CONSTANTS_A_Command_Features.EXECUTE](
433
+ @A_Inject(A_Memory) memory: A_Memory<TestResult>
434
+ ) {
435
+ memory.set('processedData', 'processed-input');
436
+ memory.set('count', 100);
437
+ memory.set('metadata', {
438
+ timestamp: new Date().toISOString(),
439
+ version: 1
440
+ });
441
+ }
442
+ }
443
+
444
+ A_Context.root.register(ResultProcessor);
445
+
446
+ const command = new ResultCommand({ input: 'test-input' });
447
+ A_Context.root.register(command);
448
+
449
+ await command.execute();
450
+
451
+ const serialized = command.toJSON();
452
+ expect(serialized.result).toBeDefined();
453
+ expect(serialized.result?.processedData).toBe('processed-input');
454
+ expect(serialized.result?.count).toBe(100);
455
+ expect(serialized.result?.metadata).toBeDefined();
456
+ expect(serialized.result?.metadata.version).toBe(1);
457
+
458
+ // Test deserialization - result is restored to memory and accessible through get method
459
+ const deserializedCommand = new ResultCommand(serialized);
460
+ const deserializedMemory = deserializedCommand.scope.resolve(A_Memory);
461
+ expect(deserializedMemory.get('processedData')).toBe('processed-input');
462
+ expect(deserializedMemory.get('count')).toBe(100);
463
+ expect(deserializedMemory.get('metadata')).toEqual(serialized.result?.metadata);
464
+ });
465
+
466
+ it('Should handle error serialization correctly', async () => {
467
+ A_Context.reset();
468
+
469
+ class ErrorCommand extends A_Command<{ input: string }, { output: string }> {}
470
+
471
+ class ErrorComponent extends A_Component {
472
+ @A_Feature.Extend({ scope: [ErrorCommand] })
473
+ async [A_CONSTANTS_A_Command_Features.EXECUTE](
474
+ @A_Inject(A_Memory) memory: A_Memory<{ output: string }>
475
+ ) {
476
+ memory.error(new A_Error({
477
+ title: 'First error',
478
+ message: 'First error message'
479
+ }));
480
+ memory.error(new A_Error({
481
+ title: 'Second error',
482
+ message: 'Second error message'
483
+ }));
484
+ throw new A_Error({ title: 'Thrown error' });
485
+ }
486
+ }
487
+
488
+ A_Context.root.register(ErrorComponent);
489
+
490
+ const command = new ErrorCommand({ input: 'test' });
491
+ A_Context.root.register(command);
492
+
493
+ await command.execute();
494
+
495
+ expect(command.isFailed).toBe(true);
496
+ expect(command.errors?.size).toBe(2);
497
+
498
+ const serialized = command.toJSON();
499
+ expect(serialized.errors).toBeDefined();
500
+ expect(serialized.errors?.length).toBe(2);
501
+ expect(serialized.errors?.[0].title).toBe('First error');
502
+ expect(serialized.errors?.[1].title).toBe('Second error');
503
+
504
+ // Test deserialization - errors are restored to memory
505
+ const deserializedCommand = new ErrorCommand(serialized);
506
+ const deserializedMemory = deserializedCommand.scope.resolve(A_Memory);
507
+ expect(deserializedMemory.Errors?.size).toBe(2);
508
+ const errorArray = Array.from(deserializedMemory.Errors?.values() || []);
509
+ expect(errorArray[0].title).toBe('First error');
510
+ expect(errorArray[1].title).toBe('Second error');
511
+ });
512
+
513
+ it('Should maintain parameter integrity across command transmission', async () => {
514
+ // Simulate command transmission across network/storage
515
+ const originalParams = {
516
+ userId: '12345',
517
+ action: 'update',
518
+ data: {
519
+ email: 'test@example.com',
520
+ preferences: {
521
+ theme: 'dark',
522
+ notifications: true
523
+ }
524
+ },
525
+ timestamp: new Date().toISOString()
526
+ };
527
+
528
+ class TransmissionCommand extends A_Command<typeof originalParams, { success: boolean }> {}
529
+
530
+ // Step 1: Create and execute original command
531
+ const originalCommand = new TransmissionCommand(originalParams);
532
+ A_Context.root.register(originalCommand);
533
+ await originalCommand.execute();
534
+
535
+ // Step 2: Serialize for transmission
536
+ const serializedForTransmission = JSON.stringify(originalCommand.toJSON());
537
+
538
+ // Step 3: Deserialize from transmission
539
+ const deserializedData = JSON.parse(serializedForTransmission);
540
+ const restoredCommand = new TransmissionCommand(deserializedData);
541
+
542
+ // Step 4: Verify parameter integrity
543
+ expect(restoredCommand.params).toEqual(originalParams);
544
+ expect(restoredCommand.params.data.email).toBe('test@example.com');
545
+ expect(restoredCommand.params.data.preferences.theme).toBe('dark');
546
+ expect(restoredCommand.aseid.toString()).toBe(originalCommand.aseid.toString());
547
+ expect(restoredCommand.code).toBe(originalCommand.code);
548
+ });
549
+
550
+ it('Should handle empty and edge case parameters', async () => {
551
+ const edgeCaseParams = {
552
+ emptyString: '',
553
+ emptyArray: [],
554
+ emptyObject: {},
555
+ zeroNumber: 0,
556
+ falseBoolean: false,
557
+ nullValue: null
558
+ };
559
+
560
+ class EdgeCaseCommand extends A_Command<typeof edgeCaseParams, {}> {}
561
+
562
+ const command = new EdgeCaseCommand(edgeCaseParams);
563
+ A_Context.root.register(command);
564
+ await command.execute();
565
+
566
+ const serialized = command.toJSON();
567
+ expect(serialized.params).toEqual(edgeCaseParams);
568
+
569
+ const deserializedCommand = new EdgeCaseCommand(serialized);
570
+ expect(deserializedCommand.params).toEqual(edgeCaseParams);
571
+ expect(deserializedCommand.params.emptyString).toBe('');
572
+ expect(deserializedCommand.params.emptyArray).toEqual([]);
573
+ expect(deserializedCommand.params.emptyObject).toEqual({});
574
+ expect(deserializedCommand.params.zeroNumber).toBe(0);
575
+ expect(deserializedCommand.params.falseBoolean).toBe(false);
576
+ expect(deserializedCommand.params.nullValue).toBe(null);
577
+ });
578
+ });
134
579
  });
@@ -0,0 +1,189 @@
1
+ import { A_Memory } from '@adaas/a-utils/lib/A-Memory/A-Memory.context';
2
+ import { A_Error } from '@adaas/a-concept';
3
+
4
+ jest.retryTimes(0);
5
+
6
+ describe('A-Memory tests', () => {
7
+
8
+ it('Should allow to create memory instance', () => {
9
+ const memory = new A_Memory();
10
+
11
+ expect(memory).toBeInstanceOf(A_Memory);
12
+ expect(memory.Errors).toBeUndefined();
13
+ });
14
+
15
+ it('Should allow to create memory with initial values', () => {
16
+ const initialValues = {
17
+ key1: 'value1',
18
+ key2: 42,
19
+ key3: { nested: 'object' }
20
+ };
21
+
22
+ const memory = new A_Memory(initialValues);
23
+
24
+ expect(memory.get('key1')).toBe('value1');
25
+ expect(memory.get('key2')).toBe(42);
26
+ expect(memory.get('key3')).toEqual({ nested: 'object' });
27
+ });
28
+
29
+ it('Should allow to set and get values', async () => {
30
+ const memory = new A_Memory<{
31
+ stringValue: string;
32
+ numberValue: number;
33
+ objectValue: { prop: string };
34
+ }>();
35
+
36
+ await memory.set('stringValue', 'test string');
37
+ await memory.set('numberValue', 123);
38
+ await memory.set('objectValue', { prop: 'test' });
39
+
40
+ expect(memory.get('stringValue')).toBe('test string');
41
+ expect(memory.get('numberValue')).toBe(123);
42
+ expect(memory.get('objectValue')).toEqual({ prop: 'test' });
43
+ });
44
+
45
+ it('Should return undefined for non-existent keys', () => {
46
+ const memory = new A_Memory<{ existingKey: string }>();
47
+
48
+ expect(memory.get('existingKey')).toBeUndefined();
49
+ expect(memory.get('nonExistentKey' as any)).toBeUndefined();
50
+ });
51
+
52
+ it('Should allow to drop values', async () => {
53
+ const memory = new A_Memory<{ key1: string; key2: number }>();
54
+
55
+ await memory.set('key1', 'value1');
56
+ await memory.set('key2', 42);
57
+
58
+ expect(memory.get('key1')).toBe('value1');
59
+ expect(memory.get('key2')).toBe(42);
60
+
61
+ await memory.drop('key1');
62
+
63
+ expect(memory.get('key1')).toBeUndefined();
64
+ expect(memory.get('key2')).toBe(42);
65
+ });
66
+
67
+ it('Should allow to clear all values', async () => {
68
+ const memory = new A_Memory<{ key1: string; key2: number }>();
69
+
70
+ await memory.set('key1', 'value1');
71
+ await memory.set('key2', 42);
72
+
73
+ expect(memory.get('key1')).toBe('value1');
74
+ expect(memory.get('key2')).toBe(42);
75
+
76
+ await memory.clear();
77
+
78
+ expect(memory.get('key1')).toBeUndefined();
79
+ expect(memory.get('key2')).toBeUndefined();
80
+ });
81
+
82
+ it('Should handle errors correctly', async () => {
83
+ const memory = new A_Memory();
84
+
85
+ expect(memory.Errors).toBeUndefined();
86
+
87
+ const error1 = new A_Error({ title: 'Error 1' });
88
+ const error2 = new A_Error({ title: 'Error 2' });
89
+
90
+ await memory.error(error1);
91
+ await memory.error(error2);
92
+
93
+ expect(memory.Errors).toBeDefined();
94
+ expect(memory.Errors?.size).toBe(2);
95
+ expect(memory.Errors?.has(error1)).toBe(true);
96
+ expect(memory.Errors?.has(error2)).toBe(true);
97
+ });
98
+
99
+ it('Should verify prerequisites correctly', async () => {
100
+ const memory = new A_Memory<{
101
+ required1: string;
102
+ required2: number;
103
+ optional?: string;
104
+ }>();
105
+
106
+ // No values set initially
107
+ expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(false);
108
+
109
+ // Set one required value
110
+ await memory.set('required1', 'value1');
111
+ expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(false);
112
+
113
+ // Set both required values
114
+ await memory.set('required2', 42);
115
+ expect(await memory.verifyPrerequisites(['required1', 'required2'])).toBe(true);
116
+
117
+ // Test with empty requirements
118
+ expect(await memory.verifyPrerequisites([])).toBe(true);
119
+ });
120
+
121
+ it('Should serialize to JSON correctly', async () => {
122
+ const memory = new A_Memory<{
123
+ stringProp: string;
124
+ numberProp: number;
125
+ objectProp: { nested: string };
126
+ arrayProp: number[];
127
+ }>();
128
+
129
+ await memory.set('stringProp', 'test');
130
+ await memory.set('numberProp', 42);
131
+ await memory.set('objectProp', { nested: 'value' });
132
+ await memory.set('arrayProp', [1, 2, 3]);
133
+
134
+ const json = memory.toJSON();
135
+
136
+ expect(json).toEqual({
137
+ stringProp: 'test',
138
+ numberProp: 42,
139
+ objectProp: { nested: 'value' },
140
+ arrayProp: [1, 2, 3]
141
+ });
142
+ });
143
+
144
+ it('Should handle objects with toJSON method in serialization', async () => {
145
+ class SerializableObject {
146
+ constructor(private value: string) {}
147
+
148
+ toJSON() {
149
+ return { serialized: this.value };
150
+ }
151
+ }
152
+
153
+ const memory = new A_Memory<{
154
+ regular: string;
155
+ serializable: SerializableObject;
156
+ }>();
157
+
158
+ await memory.set('regular', 'normal value');
159
+ await memory.set('serializable', new SerializableObject('test'));
160
+
161
+ const json = memory.toJSON();
162
+
163
+ expect(json).toEqual({
164
+ regular: 'normal value',
165
+ serializable: { serialized: 'test' }
166
+ });
167
+ });
168
+
169
+ it('Should handle null and undefined values correctly', async () => {
170
+ const memory = new A_Memory<{
171
+ nullValue: null;
172
+ undefinedValue: undefined;
173
+ stringValue: string;
174
+ }>();
175
+
176
+ await memory.set('nullValue', null);
177
+ await memory.set('undefinedValue', undefined);
178
+ await memory.set('stringValue', 'test');
179
+
180
+ expect(memory.get('nullValue')).toBe(null);
181
+ expect(memory.get('undefinedValue')).toBe(undefined);
182
+ expect(memory.get('stringValue')).toBe('test');
183
+
184
+ const json = memory.toJSON();
185
+ expect(json.nullValue).toBe(null);
186
+ expect(json.undefinedValue).toBe(undefined);
187
+ expect(json.stringValue).toBe('test');
188
+ });
189
+ });