@elysiumjs/mnemosyne 0.2.0 → 0.4.0

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.
@@ -1,505 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { Mock } from 'bun:test';
16
-
17
- import { Application, RedisModule } from '@elysiumjs/core';
18
- import { afterAll, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
19
- import { createCache } from 'cache-manager';
20
- import * as KV from 'keyv';
21
-
22
- import { Cache, MnemosyneModule } from '../src';
23
-
24
- // Mock dependencies
25
- mock.module('cache-manager', () => ({
26
- createCache: mock(createCache)
27
- }));
28
-
29
- // A minimal in-memory stand-in for Bun.RedisClient
30
- const data = new Map<string, string>();
31
- const originalRedisClient = Bun.RedisClient;
32
- const RedisClientFake = mock(function () {
33
- return {
34
- close: mock(),
35
- get: async (k: string) => data.get(k) ?? null,
36
- set: async (k: string, v: string) => {
37
- data.set(k, v);
38
- return 'OK';
39
- },
40
- del: async (k: string) => Number(data.delete(k)),
41
- exists: async (k: string) => data.has(k),
42
- mget: async (...keys: string[]) => keys.map((k) => data.get(k) ?? null),
43
- send: async (cmd: string, args: string[]) => {
44
- if (cmd === 'UNLINK') return Number(data.delete(args[0]));
45
- if (cmd === 'SCAN') return ['0', [...data.keys()]];
46
- if (cmd === 'FLUSHDB') return data.clear();
47
- return null;
48
- }
49
- };
50
- }) as unknown as typeof Bun.RedisClient;
51
-
52
- let app: Awaited<ReturnType<typeof Application.testing>>;
53
-
54
- beforeAll(async () => {
55
- Bun.RedisClient = RedisClientFake;
56
- app = await Application.testing({
57
- modules: [
58
- RedisModule.with({
59
- default: 'cache',
60
- connections: { cache: { url: 'redis://cache:6379' } }
61
- }),
62
- MnemosyneModule
63
- ]
64
- });
65
- await app.get('mnemosyne.cache.redis');
66
- await app.get('mnemosyne.cache.memory');
67
- });
68
-
69
- afterAll(async () => {
70
- // Some tests spy on Keyv without restoring it
71
- mock.restore();
72
- await app.shutdown();
73
- Bun.RedisClient = originalRedisClient;
74
- });
75
-
76
- // Test the weak cache
77
- describe('Cache.weak', () => {
78
- it('should get data from the weak cache', () => {
79
- // Create a test object to use as a context
80
- const testContext = {};
81
-
82
- // Get data from the cache (should initialize with empty object)
83
- const data = Cache.weak.get(testContext);
84
-
85
- // Check if the data is an empty object
86
- expect(data).toEqual({});
87
-
88
- // Verify that the context is now in the map
89
- expect(Cache.weak.map.has(testContext)).toBe(true);
90
- });
91
-
92
- it('should return existing data from the weak cache', () => {
93
- // Create a test object to use as a context
94
- const testContext = {};
95
-
96
- // Set some data in the cache
97
- const testData = { test: 'data' };
98
- Cache.weak.map.set(testContext, testData);
99
-
100
- // Get data from the cache
101
- const data = Cache.weak.get(testContext);
102
-
103
- // Check if the data matches what we set
104
- expect(data).toBe(testData);
105
- });
106
-
107
- it('should delete data from the weak cache', () => {
108
- // Create a test object to use as a context
109
- const testContext = {};
110
-
111
- // Set some data in the cache
112
- Cache.weak.map.set(testContext, { test: 'data' });
113
-
114
- // Verify that the context is in the map
115
- expect(Cache.weak.map.has(testContext)).toBe(true);
116
-
117
- // Delete the data
118
- Cache.weak.del(testContext);
119
-
120
- // Verify that the context is no longer in the map
121
- expect(Cache.weak.map.has(testContext)).toBe(false);
122
- });
123
- });
124
-
125
- // Test the Redis cache
126
- describe('Cache.redis', () => {
127
- beforeAll(async () => {
128
- await Cache.redis.clear();
129
- });
130
-
131
- // Reset mocks before each test
132
- beforeEach(() => {
133
- mock.restore();
134
- });
135
-
136
- it('should be created with the correct configuration', async () => {
137
- // Check if createCache was called with the correct parameters
138
- expect(createCache).toHaveBeenCalledWith({
139
- stores: expect.arrayContaining([expect.any(Object)])
140
- });
141
- });
142
-
143
- it('should call get method with the correct parameters', async () => {
144
- const getSpy = spyOn(Cache.redis, 'get');
145
-
146
- // Call the get method
147
- const result = await Cache.redis.get('test-key');
148
-
149
- // Check if the get method was called with the correct parameters
150
- expect(getSpy).toHaveBeenCalledWith('test-key');
151
-
152
- // Check if the result is correct
153
- expect(result).toBeUndefined();
154
- });
155
-
156
- it('should call set method with the correct parameters', async () => {
157
- const setSpy = spyOn(Cache.redis, 'set');
158
-
159
- // Call the set method
160
- const result = await Cache.redis.set('test-key', 'test-value', 3600);
161
-
162
- // Check if the set method was called with the correct parameters
163
- expect(setSpy).toHaveBeenCalledWith('test-key', 'test-value', 3600);
164
-
165
- // Check if the result is correct
166
- expect(result).toBe('test-value');
167
- });
168
-
169
- it('should call ttl method with the correct parameters', async () => {
170
- const ttlSpy = spyOn(Cache.redis, 'ttl');
171
-
172
- // Call the ttl method
173
- const result = await Cache.redis.ttl('test-key');
174
-
175
- // Check if the ttl method was called with the correct parameters
176
- expect(ttlSpy).toHaveBeenCalledWith('test-key');
177
-
178
- const date = new Date(result!);
179
-
180
- // Check if the result is correct
181
- expect(date.getTime()).toBeGreaterThanOrEqual(Date.now() - 3600 * 1000);
182
- });
183
-
184
- it('should call del method with the correct parameters', async () => {
185
- const delSpy = spyOn(Cache.redis, 'del');
186
-
187
- // Call the del method
188
- const result = await Cache.redis.del('test-key');
189
-
190
- // Check if the del method was called with the correct parameters
191
- expect(delSpy).toHaveBeenCalledWith('test-key');
192
-
193
- // Check if the result is correct
194
- expect(result).toBe(true);
195
- });
196
-
197
- it('should call mset method with the correct parameters', async () => {
198
- const msetSpy = spyOn(Cache.redis, 'mset');
199
-
200
- // Set up the mock to return the test data
201
- const testData = [
202
- { key: 'key1', value: 'value1', ttl: 3600 },
203
- { key: 'key2', value: 'value2' }
204
- ];
205
-
206
- // Call the mset method
207
- const result = await Cache.redis.mset(testData);
208
-
209
- // Check if the mset method was called with the correct parameters
210
- expect(msetSpy).toHaveBeenCalledWith(testData);
211
-
212
- // Check if the result is correct
213
- expect(result).toEqual(testData);
214
- });
215
-
216
- it('should call mget method with the correct parameters', async () => {
217
- const mgetSpy = spyOn(Cache.redis, 'mget');
218
-
219
- // Call the mget method
220
- const result = await Cache.redis.mget(['key1', 'key2']);
221
-
222
- // Check if the mget method was called with the correct parameters
223
- expect(mgetSpy).toHaveBeenCalledWith(['key1', 'key2']);
224
-
225
- // Check if the result is correct
226
- expect(result).toEqual(['value1', 'value2']);
227
- });
228
-
229
- it('should call mdel method with the correct parameters', async () => {
230
- const mdelSpy = spyOn(Cache.redis, 'mdel');
231
-
232
- // Call the mdel method
233
- const result = await Cache.redis.mdel(['key1', 'key2']);
234
-
235
- // Check if the mdel method was called with the correct parameters
236
- expect(mdelSpy).toHaveBeenCalledWith(['key1', 'key2']);
237
-
238
- // Check if the result is correct
239
- expect(result).toBe(true);
240
- });
241
-
242
- it('should call clear method', async () => {
243
- const clearSpy = spyOn(Cache.redis, 'clear');
244
-
245
- // Call the clear method
246
- const result = await Cache.redis.clear();
247
-
248
- // Check if the clear method was called
249
- expect(clearSpy).toHaveBeenCalled();
250
-
251
- // Check if the result is correct
252
- expect(result).toBe(true);
253
- });
254
-
255
- it('should create a tagged cache with the correct namespace', () => {
256
- const keyvSpy = spyOn(KV, 'Keyv').mockReturnThis();
257
-
258
- // Call the tags method
259
- Cache.redis.tags('tag1', 'tag2');
260
-
261
- // Check if Keyv was instantiated with the correct namespace
262
- expect(keyvSpy).toHaveBeenCalledWith({
263
- store: expect.any(Object),
264
- namespace: 'cache__tag1__tag2'
265
- });
266
-
267
- // Check if createCache was called
268
- expect(createCache).toHaveBeenCalled();
269
- });
270
- });
271
-
272
- // Test the memory cache
273
- describe('Cache.memory', () => {
274
- // Reset mocks before each test
275
- beforeEach(() => {
276
- mock.restore();
277
- });
278
-
279
- it('should be created with the correct configuration', () => {
280
- // Check if createCache was called with the correct parameters
281
- expect(createCache).toHaveBeenCalledWith({
282
- stores: expect.arrayContaining([expect.any(Object)])
283
- });
284
- });
285
-
286
- it('should call set method with the correct parameters', async () => {
287
- const setSpy = spyOn(Cache.memory, 'set');
288
-
289
- // Call the set method
290
- const result = await Cache.memory.set('test-key', 'test-value', 3600);
291
-
292
- // Check if the set method was called with the correct parameters
293
- expect(setSpy).toHaveBeenCalledWith('test-key', 'test-value', 3600);
294
-
295
- // Check if the result is correct
296
- expect(result).toBe('test-value');
297
- });
298
- it('should call get method with the correct parameters', async () => {
299
- const getSpy = spyOn(Cache.memory, 'get');
300
-
301
- // Call the get method
302
- const result = await Cache.memory.get('test-key');
303
-
304
- // Check if the get method was called with the correct parameters
305
- expect(getSpy).toHaveBeenCalledWith('test-key');
306
-
307
- // Check if the result is correct
308
- expect(result).toBe('test-value');
309
- });
310
-
311
- it('should call ttl method with the correct parameters', async () => {
312
- const ttlSpy = spyOn(Cache.memory, 'ttl');
313
-
314
- // Call the ttl method
315
- const result = await Cache.memory.ttl('test-key');
316
-
317
- // Check if the ttl method was called with the correct parameters
318
- expect(ttlSpy).toHaveBeenCalledWith('test-key');
319
-
320
- const date = new Date(result!);
321
-
322
- // Check if the result is correct
323
- expect(date.getTime()).toBeGreaterThanOrEqual(Date.now() - 3600 * 1000);
324
- });
325
-
326
- it('should call del method with the correct parameters', async () => {
327
- const delSpy = spyOn(Cache.memory, 'del');
328
-
329
- // Call the del method
330
- const result = await Cache.memory.del('test-key');
331
-
332
- // Check if the del method was called with the correct parameters
333
- expect(delSpy).toHaveBeenCalledWith('test-key');
334
-
335
- // Check if the result is correct
336
- expect(result).toBe(true);
337
- });
338
-
339
- it('should call mset method with the correct parameters', async () => {
340
- const msetSpy = spyOn(Cache.memory, 'mset');
341
-
342
- // Set up the mock to return the test data
343
- const testData = [
344
- { key: 'key1', value: 'value1', ttl: 3600 },
345
- { key: 'key2', value: 'value2' }
346
- ];
347
-
348
- // Call the mset method
349
- const result = await Cache.memory.mset(testData);
350
-
351
- // Check if the mset method was called with the correct parameters
352
- expect(msetSpy).toHaveBeenCalledWith(testData);
353
-
354
- // Check if the result is correct
355
- expect(result).toEqual(testData);
356
- });
357
-
358
- it('should call mget method with the correct parameters', async () => {
359
- const mgetSpy = spyOn(Cache.memory, 'mget');
360
-
361
- // Call the mget method
362
- const result = await Cache.memory.mget(['key1', 'key2']);
363
-
364
- // Check if the mget method was called with the correct parameters
365
- expect(mgetSpy).toHaveBeenCalledWith(['key1', 'key2']);
366
-
367
- // Check if the result is correct
368
- expect(result).toEqual(['value1', 'value2']);
369
- });
370
-
371
- it('should call mdel method with the correct parameters', async () => {
372
- const mdelSpy = spyOn(Cache.memory, 'mdel');
373
-
374
- // Call the mdel method
375
- const result = await Cache.memory.mdel(['key1', 'key2']);
376
-
377
- // Check if the mdel method was called with the correct parameters
378
- expect(mdelSpy).toHaveBeenCalledWith(['key1', 'key2']);
379
-
380
- // Check if the result is correct
381
- expect(result).toBe(true);
382
- });
383
-
384
- it('should call clear method', async () => {
385
- const clearSpy = spyOn(Cache.memory, 'clear');
386
-
387
- // Call the clear method
388
- const result = await Cache.memory.clear();
389
-
390
- // Check if the clear method was called
391
- expect(clearSpy).toHaveBeenCalled();
392
-
393
- // Check if the result is correct
394
- expect(result).toBe(true);
395
- });
396
-
397
- it('should create a tagged cache with the correct namespace', () => {
398
- const keyvSpy = spyOn(KV, 'Keyv').mockReturnThis();
399
-
400
- // Call the tags method
401
- Cache.memory.tags('tag1', 'tag2');
402
-
403
- // Check if Keyv was instantiated with the correct namespace
404
- expect(keyvSpy).toHaveBeenCalledWith({
405
- store: expect.any(Object),
406
- namespace: 'cache__tag1__tag2'
407
- });
408
-
409
- // Check if createCache was called
410
- expect(createCache).toHaveBeenCalled();
411
- });
412
- });
413
-
414
- // Test cache with different TTL values
415
- describe('Cache with TTL', () => {
416
- beforeEach(() => {
417
- mock.restore();
418
- });
419
-
420
- it('should set cache with TTL', async () => {
421
- const setSpy = spyOn(Cache.memory, 'set');
422
-
423
- // Call the set method with TTL
424
- await Cache.memory.set('test-key', 'test-value', 3600);
425
-
426
- // Check if the set method was called with the correct TTL
427
- expect(setSpy).toHaveBeenCalledWith('test-key', 'test-value', 3600);
428
- });
429
-
430
- it('should set cache without TTL', async () => {
431
- const setSpy = spyOn(Cache.memory, 'set');
432
-
433
- // Call the set method without TTL
434
- await Cache.memory.set('test-key', 'test-value');
435
-
436
- // Check if the set method was called without TTL
437
- expect(setSpy).toHaveBeenCalledWith('test-key', 'test-value');
438
- });
439
-
440
- it('should set multiple cache entries with different TTLs', async () => {
441
- const msetSpy = spyOn(Cache.memory, 'mset');
442
-
443
- // Set up the mock to return the test data
444
- const testData = [
445
- { key: 'key1', value: 'value1', ttl: 3600 },
446
- { key: 'key2', value: 'value2' }
447
- ];
448
-
449
- // Call the mset method
450
- await Cache.memory.mset(testData);
451
-
452
- // Check if the mset method was called with the correct parameters
453
- expect(msetSpy).toHaveBeenCalledWith(testData);
454
- });
455
- });
456
-
457
- // Test cache namespacing and tagging
458
- describe('Cache namespacing and tagging', () => {
459
- beforeEach(() => {
460
- mock.restore();
461
- });
462
-
463
- it('should create a tagged cache with a single tag', () => {
464
- const keyvSpy = spyOn(KV, 'Keyv').mockReturnThis();
465
-
466
- // Call the tags method with a single tag
467
- Cache.memory.tags('tag1');
468
-
469
- // Check if Keyv was instantiated with the correct namespace
470
- expect(keyvSpy).toHaveBeenCalledWith(
471
- expect.objectContaining({
472
- namespace: 'cache__tag1'
473
- })
474
- );
475
-
476
- // Check if createCache was called
477
- expect(createCache).toHaveBeenCalled();
478
- });
479
-
480
- it('should create a tagged cache with multiple tags', () => {
481
- const keyvSpy = spyOn(KV, 'Keyv').mockReturnThis();
482
-
483
- // Call the tags method with multiple tags
484
- Cache.memory.tags('tag1', 'tag2', 'tag3');
485
-
486
- // Check if Keyv was instantiated with the correct namespace
487
- expect(keyvSpy).toHaveBeenCalledWith(
488
- expect.objectContaining({
489
- namespace: 'cache__tag1__tag2__tag3'
490
- })
491
- );
492
-
493
- // Check if createCache was called
494
- expect(createCache).toHaveBeenCalled();
495
-
496
- keyvSpy.mockClear();
497
- (createCache as Mock<typeof createCache>).mockClear();
498
-
499
- // Call the tags method with multiple tags
500
- Cache.memory.tags('tag3', 'tag2', 'tag1');
501
-
502
- expect(keyvSpy).not.toHaveBeenCalled();
503
- expect(createCache).not.toHaveBeenCalled();
504
- });
505
- });
@@ -1,113 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import { Application, Service, ServiceNotFoundError } from '@elysiumjs/core';
16
- import { afterEach, describe, expect, it, jest, mock, spyOn } from 'bun:test';
17
-
18
- import { AbstractDatabase } from '../src/database';
19
-
20
- const mockDriver = {
21
- createConnection: mock((config: any) => ({ mockConnection: true, config })),
22
- withTransaction: mock((conn: any, cb: any) => cb(conn)),
23
- getRawClient: mock((_conn: any) => ({ rawClient: true }))
24
- };
25
-
26
- class TestDatabase extends AbstractDatabase<any, any> {
27
- protected driver = mockDriver;
28
- }
29
-
30
- describe('AbstractDatabase', () => {
31
- afterEach(() => {
32
- jest.restoreAllMocks();
33
- jest.clearAllMocks();
34
- });
35
-
36
- describe('getConnection', () => {
37
- it('should retrieve an existing connection', () => {
38
- const mockConnection = { mockConnection: true };
39
- const getSpy = spyOn(Service, 'get').mockReturnValue(mockConnection);
40
-
41
- const result = new TestDatabase().getConnection('test');
42
-
43
- expect(getSpy).toHaveBeenCalledWith('db.connection.test');
44
- expect(result).toBe(mockConnection);
45
- });
46
-
47
- it('should throw ServiceNotFoundError if the connection does not exist', () => {
48
- spyOn(Service, 'get').mockReturnValue(null);
49
-
50
- expect(() => new TestDatabase().getConnection('test')).toThrow(ServiceNotFoundError);
51
- });
52
-
53
- it('should return the transaction handle for the default connection', () => {
54
- const tx = { tx: true };
55
- const originalDescriptor = Object.getOwnPropertyDescriptor(Application, 'context');
56
- Object.defineProperty(Application, 'context', {
57
- get: () => ({ getStore: () => new Map([['db:tx', tx]]) }),
58
- configurable: true
59
- });
60
-
61
- try {
62
- expect(new TestDatabase().getConnection()).toBe(tx);
63
- } finally {
64
- if (originalDescriptor) Object.defineProperty(Application, 'context', originalDescriptor);
65
- }
66
- });
67
- });
68
-
69
- describe('connectionExists', () => {
70
- it('should delegate to Service.exists', () => {
71
- const existsSpy = spyOn(Service, 'exists')
72
- .mockReturnValueOnce(true)
73
- .mockReturnValueOnce(false);
74
- const db = new TestDatabase();
75
-
76
- expect(db.connectionExists('test')).toBe(true);
77
- expect(db.connectionExists('test')).toBe(false);
78
- expect(existsSpy).toHaveBeenCalledWith('db.connection.test');
79
- });
80
- });
81
-
82
- describe('getDefaultConnection', () => {
83
- it('should retrieve the default connection', () => {
84
- const originalDescriptor = Object.getOwnPropertyDescriptor(Application, 'context');
85
- Object.defineProperty(Application, 'context', {
86
- get: () => ({ getStore: () => null }),
87
- configurable: true
88
- });
89
-
90
- try {
91
- const mockConnection = { mockConnection: true };
92
- const getSpy = spyOn(Service, 'get').mockReturnValue(mockConnection);
93
-
94
- expect(new TestDatabase().getDefaultConnection()).toBe(mockConnection);
95
- expect(getSpy).toHaveBeenCalledWith('db.connection.default');
96
- } finally {
97
- if (originalDescriptor) Object.defineProperty(Application, 'context', originalDescriptor);
98
- }
99
- });
100
- });
101
-
102
- describe('getRawClient', () => {
103
- it('should return the raw client from the driver', () => {
104
- const mockConnection = { mockConnection: true };
105
- spyOn(Service, 'get').mockReturnValue(mockConnection);
106
-
107
- const result = new TestDatabase().getRawClient('test');
108
-
109
- expect(mockDriver.getRawClient).toHaveBeenCalledWith(mockConnection);
110
- expect(result).toEqual({ rawClient: true });
111
- });
112
- });
113
- });