@elysiumjs/mnemosyne 0.1.0 → 0.2.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.
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +34 -0
- package/README.md +19 -16
- package/dist/cache.d.ts +18 -1
- package/dist/database.d.ts +8 -24
- package/dist/index.d.ts +7 -15
- package/dist/index.js +1 -1
- package/dist/keyv-redis.d.ts +5 -1
- package/dist/mnemosyne.module.d.ts +44 -0
- package/dist/model.d.ts +1 -1
- package/dist/tenancy.d.ts +9 -10
- package/package.json +8 -9
- package/src/cache.ts +21 -20
- package/src/database.ts +13 -50
- package/src/index.ts +7 -27
- package/src/keyv-redis.ts +39 -26
- package/src/mnemosyne.module.ts +83 -0
- package/src/tenancy.ts +13 -30
- package/tests/cache.test.ts +51 -10
- package/tests/database.test.ts +37 -173
- package/tests/mnemosyne.module.test.ts +214 -0
- package/tests/setup.ts +0 -13
- package/.turbo/turbo-typecheck.log +0 -1
- package/coverage/.lcov.info.2cbccf8710225f8a.tmp +0 -301
- package/coverage/.lcov.info.80cb03b14eb4a841.tmp +0 -644
- package/coverage/lcov.info +0 -549
package/src/index.ts
CHANGED
|
@@ -12,8 +12,6 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import type { TenancyConfig } from './tenancy';
|
|
16
|
-
|
|
17
15
|
export type {
|
|
18
16
|
ColumnMetadata,
|
|
19
17
|
DatabaseCacheConfig,
|
|
@@ -27,31 +25,13 @@ export type {
|
|
|
27
25
|
TenancyStrategy
|
|
28
26
|
} from './interfaces';
|
|
29
27
|
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
28
|
+
export * from './database';
|
|
29
|
+
export * from './model';
|
|
30
|
+
export * from './repository';
|
|
33
31
|
|
|
34
|
-
export
|
|
35
|
-
getConfig as getTenancyConfig,
|
|
36
|
-
getCurrentTenant,
|
|
37
|
-
withTenant,
|
|
38
|
-
registerTenancyStrategy,
|
|
39
|
-
getTenancyStrategy,
|
|
40
|
-
TenantMiddleware,
|
|
41
|
-
SimpleTenantMiddleware,
|
|
42
|
-
StrictTenantMiddleware
|
|
43
|
-
} from './tenancy';
|
|
32
|
+
export * from './tenancy';
|
|
44
33
|
export type { TenancyConfig, TenancyMode, ModelTenancyConfig } from './tenancy';
|
|
45
34
|
|
|
46
|
-
export { Cache } from './cache';
|
|
47
|
-
export
|
|
48
|
-
export
|
|
49
|
-
|
|
50
|
-
// Configuration types
|
|
51
|
-
export interface MnemosyneConfig<TConnectionConfig = unknown> {
|
|
52
|
-
database?: {
|
|
53
|
-
default: string;
|
|
54
|
-
connections: Record<string, TConnectionConfig>;
|
|
55
|
-
};
|
|
56
|
-
tenancy?: TenancyConfig;
|
|
57
|
-
}
|
|
35
|
+
export { Cache, type CacheInterface, MEMORY_CACHE_SERVICE, REDIS_CACHE_SERVICE } from './cache';
|
|
36
|
+
export * from './mnemosyne.module';
|
|
37
|
+
export * from './keyv-redis';
|
package/src/keyv-redis.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type { KeyvEntry, StoredData } from 'keyv';
|
|
|
17
17
|
|
|
18
18
|
import { EventEmitter } from 'node:events';
|
|
19
19
|
|
|
20
|
-
import { Event, Redis } from '@elysiumjs/core';
|
|
20
|
+
import { Event, Redis, Service } from '@elysiumjs/core';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Keyv adapter for redis. This adapter is based on the Bun's Redis client.
|
|
@@ -68,10 +68,6 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
68
68
|
}) {
|
|
69
69
|
super();
|
|
70
70
|
|
|
71
|
-
Event.once('elysium:app:launched', () => {
|
|
72
|
-
this.#client = Redis.getConnection(props.connection);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
71
|
this.#connection = props.connection;
|
|
76
72
|
this.#namespace = props.namespace;
|
|
77
73
|
this.#keyPrefixSeparator = props.keyPrefixSeparator ?? '::';
|
|
@@ -84,8 +80,15 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
84
80
|
});
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the Redis client on first use. The async path is always safe, even when the
|
|
85
|
+
* Redis module has not been initialised yet (lazy boot).
|
|
86
|
+
*/
|
|
87
|
+
protected async getClient(): Promise<Bun.RedisClient> {
|
|
88
|
+
this.#client ??= await Service.resolve<Bun.RedisClient>(
|
|
89
|
+
Redis.getConnectionName(this.#connection)
|
|
90
|
+
);
|
|
91
|
+
return this.#client;
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
/**
|
|
@@ -118,23 +121,26 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
public async get<Value>(key: string): Promise<StoredData<Value> | undefined> {
|
|
124
|
+
const client = await this.getClient();
|
|
121
125
|
key = this.createKeyPrefix(key, this.#namespace);
|
|
122
|
-
const value = await
|
|
126
|
+
const value = await client.get(key);
|
|
123
127
|
|
|
124
128
|
return value === null ? undefined : (value as Value);
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
public async set(key: string, value: any, ttl?: number) {
|
|
132
|
+
const client = await this.getClient();
|
|
128
133
|
key = this.createKeyPrefix(key, this.#namespace);
|
|
129
134
|
|
|
130
135
|
if (ttl) {
|
|
131
|
-
await
|
|
136
|
+
await client.set(key, value, 'PX', ttl);
|
|
132
137
|
} else {
|
|
133
|
-
await
|
|
138
|
+
await client.set(key, value);
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
|
|
137
142
|
public async setMany(values: Array<KeyvEntry>): Promise<void> {
|
|
143
|
+
const client = await this.getClient();
|
|
138
144
|
// TODO: Use MULTI when available in Bun
|
|
139
145
|
const multi = [];
|
|
140
146
|
|
|
@@ -142,10 +148,10 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
142
148
|
const prefixedKey = this.createKeyPrefix(key, this.#namespace);
|
|
143
149
|
if (ttl) {
|
|
144
150
|
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-argument
|
|
145
|
-
multi.push(
|
|
151
|
+
multi.push(client.set(prefixedKey, value, 'PX', ttl));
|
|
146
152
|
} else {
|
|
147
153
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
148
|
-
multi.push(
|
|
154
|
+
multi.push(client.set(prefixedKey, value));
|
|
149
155
|
}
|
|
150
156
|
}
|
|
151
157
|
|
|
@@ -153,19 +159,20 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
public async delete(key: string): Promise<boolean> {
|
|
162
|
+
const client = await this.getClient();
|
|
156
163
|
key = this.createKeyPrefix(key, this.#namespace);
|
|
157
164
|
// TODO: Use UNLINK when available in Bun
|
|
158
|
-
const deleted = await (this.#useUnlink
|
|
159
|
-
? this.client.send('UNLINK', [key])
|
|
160
|
-
: this.client.del(key));
|
|
165
|
+
const deleted = await (this.#useUnlink ? client.send('UNLINK', [key]) : client.del(key));
|
|
161
166
|
|
|
162
167
|
return deleted > 0;
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
public async clear(): Promise<void> {
|
|
166
171
|
try {
|
|
172
|
+
const client = await this.getClient();
|
|
173
|
+
|
|
167
174
|
if (!this.#namespace && this.#noNamespaceAffectsAll) {
|
|
168
|
-
await
|
|
175
|
+
await client.send('FLUSHDB', []);
|
|
169
176
|
return;
|
|
170
177
|
}
|
|
171
178
|
|
|
@@ -175,7 +182,7 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
175
182
|
const deletePromises = [];
|
|
176
183
|
|
|
177
184
|
do {
|
|
178
|
-
const result = await
|
|
185
|
+
const result = await client.send('SCAN', [
|
|
179
186
|
cursor,
|
|
180
187
|
'MATCH',
|
|
181
188
|
match,
|
|
@@ -197,7 +204,7 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
197
204
|
keys = keys.filter((key: string) => !key.includes(this.#keyPrefixSeparator));
|
|
198
205
|
}
|
|
199
206
|
|
|
200
|
-
deletePromises.push(keys.map((key: string) =>
|
|
207
|
+
deletePromises.push(keys.map((key: string) => client.del(key)));
|
|
201
208
|
} while (cursor !== '0');
|
|
202
209
|
|
|
203
210
|
await Promise.all(deletePromises);
|
|
@@ -207,37 +214,42 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
207
214
|
}
|
|
208
215
|
|
|
209
216
|
public async has(key: string): Promise<boolean> {
|
|
217
|
+
const client = await this.getClient();
|
|
210
218
|
key = this.createKeyPrefix(key, this.#namespace);
|
|
211
|
-
return
|
|
219
|
+
return client.exists(key);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
public async hasMany(keys: string[]): Promise<boolean[]> {
|
|
223
|
+
const client = await this.getClient();
|
|
215
224
|
const multi = [];
|
|
216
225
|
|
|
217
226
|
for (const key of keys) {
|
|
218
227
|
const prefixedKey = this.createKeyPrefix(key, this.#namespace);
|
|
219
|
-
multi.push(
|
|
228
|
+
multi.push(client.exists(prefixedKey));
|
|
220
229
|
}
|
|
221
230
|
|
|
222
231
|
return Promise.all(multi);
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
public async getMany<Value>(keys: string[]): Promise<Array<StoredData<Value | undefined>>> {
|
|
235
|
+
const client = await this.getClient();
|
|
226
236
|
if (keys.length === 0) {
|
|
227
237
|
return [];
|
|
228
238
|
}
|
|
229
239
|
|
|
230
240
|
keys = keys.map((key) => this.createKeyPrefix(key, this.#namespace));
|
|
231
|
-
const values = await
|
|
241
|
+
const values = await client.mget(...keys);
|
|
232
242
|
|
|
233
243
|
return values as Value[];
|
|
234
244
|
}
|
|
235
245
|
|
|
236
246
|
public async disconnect(): Promise<void> {
|
|
237
|
-
|
|
247
|
+
// The connection is owned by the Redis module; only drop our reference to it.
|
|
248
|
+
this.#client = undefined;
|
|
238
249
|
}
|
|
239
250
|
|
|
240
251
|
public async deleteMany(keys: string[]): Promise<boolean> {
|
|
252
|
+
const client = await this.getClient();
|
|
241
253
|
let result = false;
|
|
242
254
|
|
|
243
255
|
// TODO: Use MULTI when available in Bun
|
|
@@ -246,9 +258,9 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
246
258
|
for (const key of keys) {
|
|
247
259
|
const prefixedKey = this.createKeyPrefix(key, this.#namespace);
|
|
248
260
|
if (this.#useUnlink) {
|
|
249
|
-
multi.push(
|
|
261
|
+
multi.push(client.send('UNLINK', [prefixedKey]));
|
|
250
262
|
} else {
|
|
251
|
-
multi.push(
|
|
263
|
+
multi.push(client.del(prefixedKey));
|
|
252
264
|
}
|
|
253
265
|
}
|
|
254
266
|
|
|
@@ -266,11 +278,12 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
266
278
|
public async *iterator<Value>(
|
|
267
279
|
namespace?: string
|
|
268
280
|
): AsyncGenerator<Array<string | Awaited<Value> | undefined>, void> {
|
|
281
|
+
const client = await this.getClient();
|
|
269
282
|
const match = namespace ? `${namespace}${this.#keyPrefixSeparator}*` : '*';
|
|
270
283
|
let cursor = '0';
|
|
271
284
|
|
|
272
285
|
do {
|
|
273
|
-
const result = await
|
|
286
|
+
const result = await client.send('SCAN', [cursor, 'MATCH', match, 'TYPE', 'string']);
|
|
274
287
|
|
|
275
288
|
// Parse the result
|
|
276
289
|
let [newCursor, keys] = result;
|
|
@@ -281,7 +294,7 @@ export class KeyvRedis extends EventEmitter implements KeyvStoreAdapter {
|
|
|
281
294
|
}
|
|
282
295
|
|
|
283
296
|
if (keys.length > 0) {
|
|
284
|
-
const values = await
|
|
297
|
+
const values = await client.mget(...keys);
|
|
285
298
|
for (const i of keys.keys()) {
|
|
286
299
|
const key = this.getKeyWithoutPrefix(keys[i], namespace);
|
|
287
300
|
const value = values[i];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Copyright (c) 2026-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 { ServiceFactoryDefinition } from '@elysiumjs/core';
|
|
16
|
+
|
|
17
|
+
import { Module } from '@elysiumjs/core';
|
|
18
|
+
import { KeyvCacheableMemory } from 'cacheable';
|
|
19
|
+
|
|
20
|
+
import { makeCacheInterface, MEMORY_CACHE_SERVICE, REDIS_CACHE_SERVICE } from './cache';
|
|
21
|
+
import { KeyvRedis } from './keyv-redis';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration of the {@link MnemosyneModule}.
|
|
25
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
26
|
+
*/
|
|
27
|
+
export type MnemosyneModuleConfig = {
|
|
28
|
+
cache: {
|
|
29
|
+
/** The Redis-backed cache. Set to `false` to not provide `Cache.redis`. */
|
|
30
|
+
redis?:
|
|
31
|
+
| {
|
|
32
|
+
/** The name of the Redis connection to use. */
|
|
33
|
+
connection: string;
|
|
34
|
+
}
|
|
35
|
+
| false;
|
|
36
|
+
|
|
37
|
+
/** The memory cache. Set to `false` to not provide `Cache.memory`. */
|
|
38
|
+
memory?:
|
|
39
|
+
| {
|
|
40
|
+
/** The default TTL of entries, in milliseconds. */
|
|
41
|
+
ttl: number;
|
|
42
|
+
|
|
43
|
+
/** The maximum number of entries kept. */
|
|
44
|
+
lruSize: number;
|
|
45
|
+
}
|
|
46
|
+
| false;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Provides the caches as the global services `mnemosyne.cache.redis` and
|
|
52
|
+
* `mnemosyne.cache.memory`. Tenancy config and strategy, and database connections, are
|
|
53
|
+
* registered by the database driver modules.
|
|
54
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
55
|
+
*/
|
|
56
|
+
@Module.register<MnemosyneModuleConfig>({
|
|
57
|
+
services: (config) => {
|
|
58
|
+
const services: ServiceFactoryDefinition[] = [];
|
|
59
|
+
const { redis, memory } = config.cache;
|
|
60
|
+
|
|
61
|
+
if (redis) {
|
|
62
|
+
services.push({
|
|
63
|
+
name: REDIS_CACHE_SERVICE,
|
|
64
|
+
global: true,
|
|
65
|
+
factory: () => makeCacheInterface(new KeyvRedis({ connection: redis.connection }))
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (memory) {
|
|
70
|
+
services.push({
|
|
71
|
+
name: MEMORY_CACHE_SERVICE,
|
|
72
|
+
global: true,
|
|
73
|
+
factory: () =>
|
|
74
|
+
makeCacheInterface(new KeyvCacheableMemory({ ttl: memory.ttl, lruSize: memory.lruSize }))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return services;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
export class MnemosyneModule extends Module.configurable<MnemosyneModuleConfig>({
|
|
82
|
+
defaults: { cache: { redis: { connection: 'cache' }, memory: { ttl: 60000, lruSize: 5000 } } }
|
|
83
|
+
}) {}
|
package/src/tenancy.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { Context } from '@elysiumjs/core';
|
|
16
16
|
import type { TenancyStrategy } from './interfaces';
|
|
17
17
|
|
|
18
|
-
import { Application, Middleware } from '@elysiumjs/core';
|
|
18
|
+
import { Application, Middleware, Service } from '@elysiumjs/core';
|
|
19
19
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
// Types
|
|
@@ -95,53 +95,36 @@ export interface ModelTenancyConfig {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// ============================================================================
|
|
98
|
-
//
|
|
98
|
+
// Configuration
|
|
99
99
|
// ============================================================================
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
/** Name of the global service holding the {@link TenancyConfig}, registered by driver modules. */
|
|
102
|
+
export const TENANCY_CONFIG_SERVICE = 'mnemosyne.tenancy.config';
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// ============================================================================
|
|
104
|
+
/** Name of the global service holding the {@link TenancyStrategy}, registered by driver modules. */
|
|
105
|
+
export const TENANCY_STRATEGY_SERVICE = 'mnemosyne.tenancy.strategy';
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Gets the current global tenancy configuration.
|
|
109
109
|
*
|
|
110
|
-
* Reads
|
|
111
|
-
* configuration if
|
|
110
|
+
* Reads the global service `mnemosyne.tenancy.config`. Returns a default
|
|
111
|
+
* configuration if none is registered.
|
|
112
112
|
*
|
|
113
113
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
114
114
|
* @returns The current tenancy configuration.
|
|
115
115
|
*/
|
|
116
|
-
export const
|
|
117
|
-
|
|
118
|
-
if (!app) {
|
|
119
|
-
return { mode: 'schema' };
|
|
120
|
-
}
|
|
121
|
-
const config = app.getConfig<{ tenancy?: TenancyConfig }>('elysium:mnemosyne' as any);
|
|
122
|
-
return config?.tenancy ?? { mode: 'schema' };
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// ============================================================================
|
|
126
|
-
// Strategy Registry
|
|
127
|
-
// ============================================================================
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Registers the tenancy strategy to use for table resolution and isolation.
|
|
131
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
132
|
-
* @param strategy The tenancy strategy to register.
|
|
133
|
-
*/
|
|
134
|
-
export const registerTenancyStrategy = (strategy: TenancyStrategy<unknown, unknown>): void => {
|
|
135
|
-
_strategy = strategy;
|
|
116
|
+
export const getTenancyConfig = (): TenancyConfig => {
|
|
117
|
+
return Service.get<TenancyConfig>(TENANCY_CONFIG_SERVICE) ?? { mode: 'schema' };
|
|
136
118
|
};
|
|
137
119
|
|
|
138
120
|
/**
|
|
139
|
-
* Gets the
|
|
121
|
+
* Gets the tenancy strategy registered by the driver module as the global service
|
|
122
|
+
* `mnemosyne.tenancy.strategy`.
|
|
140
123
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
141
124
|
* @returns The current tenancy strategy, or `null` if none is registered.
|
|
142
125
|
*/
|
|
143
126
|
export const getTenancyStrategy = (): TenancyStrategy<unknown, unknown> | null => {
|
|
144
|
-
return
|
|
127
|
+
return Service.get<TenancyStrategy<unknown, unknown>>(TENANCY_STRATEGY_SERVICE);
|
|
145
128
|
};
|
|
146
129
|
|
|
147
130
|
// ============================================================================
|
package/tests/cache.test.ts
CHANGED
|
@@ -14,21 +14,63 @@
|
|
|
14
14
|
|
|
15
15
|
import type { Mock } from 'bun:test';
|
|
16
16
|
|
|
17
|
+
import { Application, RedisModule } from '@elysiumjs/core';
|
|
17
18
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
18
19
|
import { createCache } from 'cache-manager';
|
|
19
20
|
import * as KV from 'keyv';
|
|
20
21
|
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
import { Cache } from '../src/cache';
|
|
22
|
+
import { Cache, MnemosyneModule } from '../src';
|
|
24
23
|
|
|
25
24
|
// Mock dependencies
|
|
26
25
|
mock.module('cache-manager', () => ({
|
|
27
26
|
createCache: mock(createCache)
|
|
28
27
|
}));
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
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;
|
|
32
74
|
});
|
|
33
75
|
|
|
34
76
|
// Test the weak cache
|
|
@@ -82,9 +124,8 @@ describe('Cache.weak', () => {
|
|
|
82
124
|
|
|
83
125
|
// Test the Redis cache
|
|
84
126
|
describe('Cache.redis', () => {
|
|
85
|
-
beforeAll(() => {
|
|
86
|
-
Cache.redis.clear();
|
|
87
|
-
Event.emit('elysium:app:launched', null, null);
|
|
127
|
+
beforeAll(async () => {
|
|
128
|
+
await Cache.redis.clear();
|
|
88
129
|
});
|
|
89
130
|
|
|
90
131
|
// Reset mocks before each test
|
|
@@ -109,7 +150,7 @@ describe('Cache.redis', () => {
|
|
|
109
150
|
expect(getSpy).toHaveBeenCalledWith('test-key');
|
|
110
151
|
|
|
111
152
|
// Check if the result is correct
|
|
112
|
-
expect(result).
|
|
153
|
+
expect(result).toBeUndefined();
|
|
113
154
|
});
|
|
114
155
|
|
|
115
156
|
it('should call set method with the correct parameters', async () => {
|
|
@@ -443,7 +484,7 @@ describe('Cache namespacing and tagging', () => {
|
|
|
443
484
|
Cache.memory.tags('tag1', 'tag2', 'tag3');
|
|
444
485
|
|
|
445
486
|
// Check if Keyv was instantiated with the correct namespace
|
|
446
|
-
expect(keyvSpy).
|
|
487
|
+
expect(keyvSpy).toHaveBeenCalledWith(
|
|
447
488
|
expect.objectContaining({
|
|
448
489
|
namespace: 'cache__tag1__tag2__tag3'
|
|
449
490
|
})
|