@node-c/data-redis 1.0.0-alpha63 → 1.0.0-beta0

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.
Files changed (34) hide show
  1. package/dist/repository/redis.repository.module.js +1 -1
  2. package/dist/repository/redis.repository.module.js.map +1 -1
  3. package/dist/repository/redis.repository.service.d.ts +6 -3
  4. package/dist/repository/redis.repository.service.js +83 -20
  5. package/dist/repository/redis.repository.service.js.map +1 -1
  6. package/dist/store/redis.store.module.js +1 -1
  7. package/dist/store/redis.store.module.js.map +1 -1
  8. package/dist/store/redis.store.service.js +1 -1
  9. package/dist/store/redis.store.service.js.map +1 -1
  10. package/package.json +4 -4
  11. package/src/common/definitions/common.constants.ts +10 -0
  12. package/src/common/definitions/index.ts +1 -0
  13. package/src/entityService/index.ts +2 -0
  14. package/src/entityService/redis.entity.service.definitions.ts +73 -0
  15. package/src/entityService/redis.entity.service.spec.ts +190 -0
  16. package/src/entityService/redis.entity.service.ts +291 -0
  17. package/src/index.ts +5 -0
  18. package/src/module/index.ts +2 -0
  19. package/src/module/redis.module.definitions.ts +18 -0
  20. package/src/module/redis.module.spec.ts +80 -0
  21. package/src/module/redis.module.ts +31 -0
  22. package/src/repository/index.ts +3 -0
  23. package/src/repository/redis.repository.definitions.ts +97 -0
  24. package/src/repository/redis.repository.module.spec.ts +60 -0
  25. package/src/repository/redis.repository.module.ts +34 -0
  26. package/src/repository/redis.repository.service.ts +657 -0
  27. package/src/repository/redis.repository.spec.ts +384 -0
  28. package/src/store/index.ts +3 -0
  29. package/src/store/redis.store.definitions.ts +25 -0
  30. package/src/store/redis.store.module.spec.ts +70 -0
  31. package/src/store/redis.store.module.ts +34 -0
  32. package/src/store/redis.store.service.spec.ts +392 -0
  33. package/src/store/redis.store.service.ts +391 -0
  34. package/src/vitest.config.ts +9 -0
@@ -0,0 +1,384 @@
1
+ import { AppConfigDataNoSQL, ApplicationError, ConfigProviderService } from '@node-c/core';
2
+
3
+ import classValidator from 'class-validator';
4
+ import { mergeDeepRight } from 'ramda';
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+
7
+ import {
8
+ EntitySchema,
9
+ EntitySchemaColumnType,
10
+ PrepareOptions,
11
+ RedisRepositoryService,
12
+ SaveOptions,
13
+ SaveOptionsOnConflict
14
+ } from './index';
15
+
16
+ import { Constants } from '../common/definitions';
17
+ import { RedisStoreService } from '../store';
18
+
19
+ interface DummyFindOptions {
20
+ exactSearch?: boolean;
21
+ filters?: Record<string, string>;
22
+ findAll: boolean;
23
+ page?: number;
24
+ perPage?: number;
25
+ withValues?: boolean;
26
+ }
27
+
28
+ class TestRedisRepositoryService<Entity> extends RedisRepositoryService<Entity> {
29
+ public exposePrepare(data: Entity, options?: PrepareOptions) {
30
+ return this.prepare(data, options);
31
+ }
32
+ }
33
+
34
+ describe('RedisRepositoryService', () => {
35
+ const moduleName = 'test';
36
+ const storeKey = 'test-store';
37
+ const configProvider = {
38
+ config: { data: { [moduleName]: { storeKey } } }
39
+ } as unknown as ConfigProviderService;
40
+ let dummySchema: EntitySchema;
41
+ let dummyStore: RedisStoreService;
42
+ let repository: RedisRepositoryService<unknown>;
43
+
44
+ beforeEach(() => {
45
+ delete (configProvider.config.data[moduleName] as AppConfigDataNoSQL).storeDelimiter;
46
+ // Create a dummy schema with two primary key columns: 'id' and 'code'
47
+ dummySchema = {
48
+ name: 'TestEntity',
49
+ columns: {
50
+ id: { primary: true, primaryOrder: 0 },
51
+ code: { primary: true, primaryOrder: 1 },
52
+ description: { primary: false }
53
+ }
54
+ };
55
+ dummyStore = {
56
+ scan: vi.fn()
57
+ } as unknown as RedisStoreService;
58
+ // Instantiate the repository service with the dummy schema and store.
59
+ repository = new RedisRepositoryService(
60
+ configProvider,
61
+ moduleName,
62
+ dummySchema,
63
+ dummyStore as unknown as RedisStoreService
64
+ );
65
+ });
66
+
67
+ describe('constructor', () => {
68
+ it('should throw an error if a primary key does not have the primaryOrder field provided in it for the config', () => {
69
+ // Access the protected primaryKeys property using a type assertion.
70
+ let errorMessage: string;
71
+ try {
72
+ new RedisRepositoryService(
73
+ configProvider,
74
+ moduleName,
75
+ {
76
+ ...dummySchema,
77
+ columns: { ...dummySchema.columns, id: { ...dummySchema.columns.id, primaryOrder: undefined } }
78
+ },
79
+ dummyStore
80
+ );
81
+ } catch (err) {
82
+ errorMessage = (err as ApplicationError).message;
83
+ }
84
+ expect(errorMessage!).toEqual(
85
+ 'At schema "TestEntity", column "id": the field "primaryOrder" is required for primary key columns.'
86
+ );
87
+ });
88
+ it('should extract primary keys from the provided schema and set the storeDelimiter to the default, if none is provided in the config', () => {
89
+ // Access the protected primaryKeys property using a type assertion.
90
+ const primaryKeys = (repository as unknown as { primaryKeys: string[] }).primaryKeys;
91
+ const storeDelimiter = (repository as unknown as { storeDelimiter: string[] }).storeDelimiter;
92
+ expect(primaryKeys).toEqual(['id', 'code']);
93
+ expect(storeDelimiter).toEqual(Constants.DEFAULT_STORE_DELIMITER);
94
+ });
95
+ it('should extract primary keys from the provided schema and set the storeDelimiter correctly, if one is provided in the config', () => {
96
+ (configProvider.config.data[moduleName] as AppConfigDataNoSQL).storeDelimiter = ':';
97
+ repository = new RedisRepositoryService(
98
+ configProvider,
99
+ moduleName,
100
+ dummySchema,
101
+ dummyStore as unknown as RedisStoreService
102
+ );
103
+ // Access the protected primaryKeys property using a type assertion.
104
+ const primaryKeys = (repository as unknown as { primaryKeys: string[] }).primaryKeys;
105
+ const storeDelimiter = (repository as unknown as { storeDelimiter: string[] }).storeDelimiter;
106
+ expect(primaryKeys).toEqual(['id', 'code']);
107
+ expect(storeDelimiter).toEqual(':');
108
+ });
109
+ });
110
+
111
+ describe('find', () => {
112
+ it('should return results when filters are provided with pagination options', async () => {
113
+ // Set up dummy scan to return a resolved array.
114
+ (dummyStore.scan as ReturnType<typeof vi.fn>).mockResolvedValue(['result1', 'result2']);
115
+ const options: DummyFindOptions = {
116
+ filters: { id: '123', code: 'XYZ' },
117
+ findAll: false,
118
+ page: 2,
119
+ perPage: 50
120
+ };
121
+ const result = await repository.find(options);
122
+ // The storeEntityKey is computed as '-' + ["123", "XYZ"].join('-') === "-123-XYZ"
123
+ // The full key becomes "TestEntity-123-XYZ"
124
+ // Pagination: count = perPage (50), cursor = (page - 1) * count = 50.
125
+ expect(dummyStore.scan).toHaveBeenCalledWith('TestEntity-123-XYZ', {
126
+ count: 50,
127
+ cursor: 50,
128
+ scanAll: false,
129
+ withValues: true
130
+ });
131
+ expect(result).toEqual(['result1', 'result2']);
132
+ });
133
+ it('should throw an error when exactSearch is true and a required primary key is missing', async () => {
134
+ // Provide filters that are missing a value for one primary key.
135
+ const options: DummyFindOptions = {
136
+ filters: { id: '123' }, // missing 'code'
137
+ exactSearch: true,
138
+ findAll: false
139
+ };
140
+ await expect(repository.find(options)).rejects.toThrow(
141
+ '[RedisRepositoryService TestEntity][Find Error]: The primary key field code is required when exactSearch is set to true.'
142
+ );
143
+ });
144
+ it('should call scan with a * key when some filters are not provided and exactSearch is not true', async () => {
145
+ (dummyStore.scan as ReturnType<typeof vi.fn>).mockResolvedValue(['resultAll']);
146
+ const options: DummyFindOptions = {
147
+ filters: { id: '123' }, // missing 'code'
148
+ findAll: true,
149
+ withValues: false
150
+ };
151
+ const result = await repository.find(options);
152
+ // Key should be just "TestEntity" and options merged with {}.
153
+ expect(dummyStore.scan).toHaveBeenCalledWith('TestEntity-123-*', { scanAll: true, withValues: false });
154
+ expect(result).toEqual(['resultAll']);
155
+ });
156
+ it('should call scan with the entity name when filters are not provided and findAll is true', async () => {
157
+ // When filters is undefined and findAll is true, storeEntityKey remains empty.
158
+ (dummyStore.scan as ReturnType<typeof vi.fn>).mockResolvedValue(['resultAll']);
159
+ const options: DummyFindOptions = {
160
+ findAll: true,
161
+ withValues: false
162
+ };
163
+ const result = await repository.find(options);
164
+ // Key should be just "TestEntity" and options merged with {}.
165
+ expect(dummyStore.scan).toHaveBeenCalledWith('TestEntity', { scanAll: true, withValues: false });
166
+ expect(result).toEqual(['resultAll']);
167
+ });
168
+ it('should throw an error when neither filters are provided nor findAll is true', async () => {
169
+ // Filters is undefined and findAll is false.
170
+ const options: DummyFindOptions = {
171
+ findAll: false
172
+ };
173
+ // Expect an error regarding missing filters or findAll.
174
+ await expect(repository.find(options)).rejects.toThrow(
175
+ '[RedisRepositoryService TestEntity][Error]: Either filters or findAll is required when calling the find method.'
176
+ );
177
+ });
178
+ it('should use default pagination options when page and perPage are not provided', async () => {
179
+ // Filters provided but without page or perPage.
180
+ (dummyStore.scan as ReturnType<typeof vi.fn>).mockResolvedValue(['defaultPaginatedResult']);
181
+ const options: DummyFindOptions = {
182
+ filters: { id: 'abc', code: 'def' },
183
+ findAll: false
184
+ };
185
+ const result = await repository.find(options);
186
+ // The computed storeEntityKey: '-' + ["abc", "def"].join('-') = "-abc-def"
187
+ // Key becomes "TestEntity-abc-def". Pagination defaults: count = 100 and cursor = 0.
188
+ expect(dummyStore.scan).toHaveBeenCalledWith('TestEntity-abc-def', {
189
+ count: 100,
190
+ cursor: 0,
191
+ scanAll: false,
192
+ withValues: true
193
+ });
194
+ expect(result).toEqual(['defaultPaginatedResult']);
195
+ });
196
+ it('should compute withValues as false when explicitly provided', async () => {
197
+ // Filters provided with withValues set to false.
198
+ (dummyStore.scan as ReturnType<typeof vi.fn>).mockResolvedValue(['resultWithFalse']);
199
+ const options: DummyFindOptions = {
200
+ filters: { id: '1', code: '2' },
201
+ findAll: false,
202
+ withValues: false
203
+ };
204
+ const result = await repository.find(options);
205
+ // storeEntityKey: '-' + ["1", "2"] = "-1-2", key = "TestEntity-1-2"
206
+ expect(dummyStore.scan).toHaveBeenCalledWith('TestEntity-1-2', {
207
+ count: 100,
208
+ cursor: 0,
209
+ scanAll: false,
210
+ withValues: false
211
+ });
212
+ expect(result).toEqual(['resultWithFalse']);
213
+ });
214
+ });
215
+
216
+ describe('prepare', () => {
217
+ let dummySchema: EntitySchema;
218
+ let dummyStore: RedisStoreService;
219
+ let repository: TestRedisRepositoryService<unknown>;
220
+ beforeEach(() => {
221
+ dummySchema = { name: 'TestEntity', columns: { id: { primary: true, primaryOrder: 0, generated: false } } };
222
+ dummyStore = { get: vi.fn(), set: vi.fn() } as unknown as RedisStoreService;
223
+ repository = new TestRedisRepositoryService(configProvider, moduleName, dummySchema, dummyStore);
224
+ });
225
+ it('returns prepared data and storeEntityKey for non-generated PK when value exists', async () => {
226
+ const data = { id: 'abc' };
227
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
228
+ const result = await repository.exposePrepare(data);
229
+ expect(result.data).toEqual(data);
230
+ expect(result.storeEntityKey).toEqual('abc');
231
+ });
232
+ it('returns prepared data and storeEntityKey for generated PK when value exists', async () => {
233
+ dummySchema = { name: 'TestEntity', columns: { id: { primary: true, primaryOrder: 0, generated: true } } };
234
+ repository = new TestRedisRepositoryService(configProvider, moduleName, dummySchema, dummyStore);
235
+ const data = { id: 'abc' };
236
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
237
+ const result = await repository.exposePrepare(data);
238
+ expect(result.data).toEqual(data);
239
+ expect(result.storeEntityKey).toEqual('abc');
240
+ });
241
+ it('throws error for non-generated PK when value is missing', async () => {
242
+ await expect(repository.exposePrepare({})).rejects.toThrow(
243
+ '[RedisRepositoryService TestEntity][Validation Error]: A value is required for non-generated PK column id'
244
+ );
245
+ });
246
+ it('throws error for generated PK when value is missing and generatePrimaryKeys is false', async () => {
247
+ dummySchema.columns = {
248
+ id: { primary: true, primaryOrder: 0, generated: true, type: EntitySchemaColumnType.Integer }
249
+ };
250
+ const options: PrepareOptions = { generatePrimaryKeys: false };
251
+ await expect(repository.exposePrepare({}, options)).rejects.toThrow(
252
+ '[RedisRepositoryService TestEntity][Validation Error]: A value is required for generated PK column id when the generatePrimaryKeys is set to false.'
253
+ );
254
+ });
255
+ it('throws error for generated PK when the column type is not supported for generation', async () => {
256
+ dummySchema = {
257
+ name: 'TestEntity',
258
+ columns: { id: { primary: true, primaryOrder: 0, generated: true, type: EntitySchemaColumnType.String } }
259
+ };
260
+ repository = new TestRedisRepositoryService(configProvider, moduleName, dummySchema, dummyStore);
261
+ await expect(repository.exposePrepare({}, { generatePrimaryKeys: true })).rejects.toThrow(
262
+ '[RedisRepositoryService TestEntity][Validation Error]: Unrecognized type "string" for PK column id'
263
+ );
264
+ });
265
+ it('generates integer PK when missing and generatePrimaryKeys is true and the redis store returns a value', async () => {
266
+ dummySchema.columns = {
267
+ id: { primary: true, primaryOrder: 0, generated: true, type: EntitySchemaColumnType.Integer }
268
+ };
269
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue(5);
270
+ const options: PrepareOptions = { generatePrimaryKeys: true };
271
+ const result = await repository.exposePrepare({}, options);
272
+ expect((result.data as { id: string }).id).toEqual(6);
273
+ expect(result.storeEntityKey).toEqual('6');
274
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-increment-id', 6);
275
+ });
276
+ it('generates integer PK when missing and generatePrimaryKeys is true and the store does not return a value', async () => {
277
+ dummySchema.columns = {
278
+ id: { primary: true, primaryOrder: 0, generated: true, type: EntitySchemaColumnType.Integer }
279
+ };
280
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
281
+ const options: PrepareOptions = { generatePrimaryKeys: true };
282
+ const result = await repository.exposePrepare({}, options);
283
+ expect((result.data as { id: string }).id).toEqual(1);
284
+ expect(result.storeEntityKey).toEqual('1');
285
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-increment-id', 1);
286
+ });
287
+ it('generates UUIDV4 PK when missing and generatePrimaryKeys is true', async () => {
288
+ dummySchema.columns = {
289
+ id: { primary: true, primaryOrder: 0, generated: true, type: EntitySchemaColumnType.UUIDV4 }
290
+ };
291
+ const options: PrepareOptions = { generatePrimaryKeys: true };
292
+ const result = await repository.exposePrepare({}, options);
293
+ const resultData = result.data as { id: string };
294
+ expect(resultData.id).toHaveLength(36);
295
+ expect(resultData.id.match(/\-/)).toBe(null);
296
+ expect(result.storeEntityKey).toHaveLength(36);
297
+ expect(result.storeEntityKey.match(/\-/)).toBe(null);
298
+ });
299
+ it('throws validation error when optValidate is true and validate returns errors', async () => {
300
+ const data = { id: 'value' };
301
+ const validateSpy = vi.spyOn(classValidator, 'validate').mockResolvedValue([{ property: 'id' }]);
302
+ const options: PrepareOptions = { validate: true };
303
+ await expect(repository.exposePrepare(data, options)).rejects.toThrow(
304
+ '[RedisRepositoryService TestEntity][Validation Error]:'
305
+ );
306
+ validateSpy.mockRestore();
307
+ });
308
+ it('throws unique error when onConflict is ThrowError and existing entry exists', async () => {
309
+ dummySchema.columns = { id: { primary: true, primaryOrder: 0, generated: false } };
310
+ const data = { id: 'unique' };
311
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue('existing');
312
+ const options: PrepareOptions = { onConflict: SaveOptionsOnConflict.ThrowError };
313
+ await expect(repository.exposePrepare(data, options)).rejects.toThrow(
314
+ '[RedisRepositoryService TestEntity][Unique Error]: An entry already exists for key unique.'
315
+ );
316
+ });
317
+ it('merges data when onConflict is Update and existing entry exists', async () => {
318
+ dummySchema.columns = { id: { primary: true, primaryOrder: 0, generated: false }, name: { primary: false } };
319
+ const data = { id: 'unique', name: 'new' };
320
+ const existingData = { id: 'unique', name: 'old', extra: 'x' };
321
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue(JSON.stringify(existingData));
322
+ const options: PrepareOptions = { onConflict: SaveOptionsOnConflict.Update };
323
+ const result = await repository.exposePrepare(data, options);
324
+ const expected = mergeDeepRight(existingData, data);
325
+ expect(result.data).toEqual(expected);
326
+ expect(result.storeEntityKey).toEqual('unique');
327
+ });
328
+ it('throws error when onConflict has invalid value', async () => {
329
+ dummySchema.columns = { id: { primary: true, primaryOrder: 0, generated: false } };
330
+ const data = { id: 'unique' };
331
+ (dummyStore.get as ReturnType<typeof vi.fn>).mockResolvedValue('existing');
332
+ const options: PrepareOptions = { onConflict: 'invalidConflict' as SaveOptionsOnConflict };
333
+ await expect(repository.exposePrepare(data, options)).rejects.toThrow(
334
+ 'Invalid value "invalidConflict" provided for onConflict.'
335
+ );
336
+ });
337
+ });
338
+
339
+ describe('save', () => {
340
+ let dummySchema: EntitySchema;
341
+ let dummyStore: RedisStoreService;
342
+ let repository: TestRedisRepositoryService<unknown>;
343
+ beforeEach(() => {
344
+ dummySchema = { name: 'TestEntity', columns: { id: { primary: true, primaryOrder: 0, generated: false } } };
345
+ dummyStore = {
346
+ delete: vi.fn().mockResolvedValue(0),
347
+ get: vi.fn().mockResolvedValue(undefined),
348
+ set: vi.fn().mockResolvedValue(undefined)
349
+ } as unknown as RedisStoreService;
350
+ repository = new TestRedisRepositoryService(configProvider, moduleName, dummySchema, dummyStore);
351
+ });
352
+ it('calls delete branch when options.delete is true', async () => {
353
+ const data = { id: 'abc', value: 123 };
354
+ const options: SaveOptions = { delete: true, transactionId: 'txn1' };
355
+ const result = await repository.save(data, options);
356
+ expect(dummyStore.delete).toHaveBeenCalledWith(['abc'], { transactionId: 'txn1' });
357
+ expect(result).toEqual(['abc']);
358
+ });
359
+ it('saves data normally when no options are provided', async () => {
360
+ const data = { id: 'xyz', value: 'test' };
361
+ const result = await repository.save(data);
362
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-xyz', data, { transactionId: undefined });
363
+ expect(result).toEqual([data]);
364
+ });
365
+ it('saves data normally when delete option is not provided (single entity)', async () => {
366
+ const data = { id: 'xyz', value: 'test' };
367
+ const options: SaveOptions = { transactionId: 'txn2' };
368
+ const result = await repository.save(data, options);
369
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-xyz', data, { transactionId: 'txn2' });
370
+ expect(result).toEqual([data]);
371
+ });
372
+ it('saves data normally when delete option is not provided (multiple entities)', async () => {
373
+ const dataArray = [
374
+ { id: '1', value: 'a' },
375
+ { id: '2', value: 'b' }
376
+ ];
377
+ const options: SaveOptions = { transactionId: 'txn3' };
378
+ const result = await repository.save(dataArray, options);
379
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-1', dataArray[0], { transactionId: 'txn3' });
380
+ expect(dummyStore.set).toHaveBeenCalledWith('TestEntity-2', dataArray[1], { transactionId: 'txn3' });
381
+ expect(result).toEqual(dataArray);
382
+ });
383
+ });
384
+ });
@@ -0,0 +1,3 @@
1
+ export * from './redis.store.definitions';
2
+ export * from './redis.store.module';
3
+ export * from './redis.store.service';
@@ -0,0 +1,25 @@
1
+ export interface GetOptions {
2
+ parseToJSON?: boolean;
3
+ withValues?: boolean;
4
+ }
5
+
6
+ export interface RedisStoreModuleOptions {
7
+ dataModuleName: string;
8
+ }
9
+
10
+ export interface ScanOptions {
11
+ count?: number;
12
+ cursor?: number;
13
+ parseToJSON?: boolean;
14
+ scanAll?: boolean;
15
+ withValues?: boolean;
16
+ }
17
+
18
+ export interface SetOptions {
19
+ transactionId?: string;
20
+ ttl?: number;
21
+ }
22
+
23
+ export interface StoreDeleteOptions {
24
+ transactionId?: string;
25
+ }
@@ -0,0 +1,70 @@
1
+ import { Provider } from '@nestjs/common';
2
+ import { ConfigProviderService } from '@node-c/core';
3
+ import { RedisClientType } from 'redis';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+
6
+ import { RedisStoreModule, RedisStoreService } from './index';
7
+
8
+ import { Constants } from '../common/definitions';
9
+
10
+ describe('RedisStoreModule', () => {
11
+ describe('register', () => {
12
+ const dummyOptions = {
13
+ dataModuleName: 'testData'
14
+ };
15
+ const serviceToken = `${dummyOptions.dataModuleName}${Constants.REDIS_CLIENT_STORE_SERVICE_SUFFIX}`;
16
+ it('should return a dynamic module with the expected configuration', () => {
17
+ const dynamicModule = RedisStoreModule.register(dummyOptions);
18
+ expect(dynamicModule).toEqual({
19
+ global: true,
20
+ module: RedisStoreModule,
21
+ providers: [
22
+ {
23
+ provide: Constants.REDIS_CLIENT,
24
+ useFactory: expect.any(Function),
25
+ inject: [ConfigProviderService]
26
+ },
27
+ { provide: Constants.REDIS_CLIENT_DATA_MODULE_NAME, useValue: dummyOptions.dataModuleName },
28
+ { provide: serviceToken, useClass: RedisStoreService }
29
+ ],
30
+ exports: [{ provide: serviceToken, useClass: RedisStoreService }]
31
+ });
32
+ });
33
+ it('should have a useFactory that calls RedisStoreService.createClient with correct parameters', async () => {
34
+ const dynamicModule = RedisStoreModule.register(dummyOptions);
35
+ // Locate the provider for Constants.REDIS_CLIENT.
36
+ const clientProvider = dynamicModule.providers!.find(
37
+ ((provider: { provide: unknown }) => provider.provide === Constants.REDIS_CLIENT) as unknown as (
38
+ _value: Provider<unknown>,
39
+ _index: number,
40
+ _obj: Provider<unknown>[]
41
+ ) => unknown
42
+ ) as unknown as { useFactory: (_arg: unknown) => unknown };
43
+ expect(clientProvider).toBeDefined();
44
+ // Create a fake config provider with a minimal config.
45
+ const fakeConfigProvider: ConfigProviderService = {
46
+ config: {
47
+ data: {
48
+ [dummyOptions.dataModuleName]: {
49
+ password: 'secret',
50
+ host: 'localhost',
51
+ port: 1234
52
+ }
53
+ }
54
+ }
55
+ } as ConfigProviderService;
56
+ // Spy on RedisStoreService.createClient.
57
+ const dummyClient = {} as ReturnType<typeof RedisStoreService.createClient>;
58
+ const createClientSpy = vi
59
+ .spyOn(RedisStoreService, 'createClient')
60
+ .mockResolvedValue(dummyClient as unknown as RedisClientType);
61
+ // Call the useFactory function and check the results.
62
+ const result = await clientProvider.useFactory(fakeConfigProvider);
63
+ expect(createClientSpy).toHaveBeenCalledWith(fakeConfigProvider.config, {
64
+ dataModuleName: dummyOptions.dataModuleName
65
+ });
66
+ expect(result).toBe(dummyClient);
67
+ createClientSpy.mockRestore();
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,34 @@
1
+ import { DynamicModule, Module } from '@nestjs/common';
2
+
3
+ import { ConfigProviderService, Constants as CoreConstants } from '@node-c/core';
4
+
5
+ import { RedisStoreModuleOptions } from './redis.store.definitions';
6
+ import { RedisStoreService } from './redis.store.service';
7
+
8
+ import { Constants } from '../common/definitions';
9
+
10
+ @Module({})
11
+ export class RedisStoreModule {
12
+ static register(options: RedisStoreModuleOptions): DynamicModule {
13
+ const { dataModuleName } = options;
14
+ const serviceToken = `${dataModuleName}${Constants.REDIS_CLIENT_STORE_SERVICE_SUFFIX}`;
15
+ return {
16
+ global: true,
17
+ module: RedisStoreModule,
18
+ providers: [
19
+ {
20
+ provide: Constants.REDIS_CLIENT,
21
+ useFactory: async (configProvider: ConfigProviderService) => {
22
+ // this is purposfully split like this, so we can place debug logs in between when needed :D
23
+ const client = await RedisStoreService.createClient(configProvider.config, { dataModuleName });
24
+ return client;
25
+ },
26
+ inject: [ConfigProviderService]
27
+ },
28
+ { provide: CoreConstants.DATA_MODULE_NAME, useValue: dataModuleName },
29
+ { provide: serviceToken, useClass: RedisStoreService }
30
+ ],
31
+ exports: [{ provide: serviceToken, useClass: RedisStoreService }]
32
+ };
33
+ }
34
+ }