@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,190 @@
1
+ import type { DataFindResults } from '@node-c/core';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import {
5
+ BulkCreateOptions,
6
+ CreateOptions,
7
+ DeleteOptions,
8
+ FindOneOptions,
9
+ RedisEntityService,
10
+ UpdateOptions
11
+ } from './index';
12
+
13
+ import type { RedisRepositoryService } from '../repository';
14
+ import type { RedisStoreService } from '../store';
15
+
16
+ interface DummyEntity {
17
+ createdAt?: Date;
18
+ id: string;
19
+ updatedAt?: Date;
20
+ value?: number;
21
+ }
22
+ const entity1: DummyEntity = { createdAt: new Date(), id: '1', updatedAt: new Date(), value: 10 };
23
+ const entity2: DummyEntity = { createdAt: new Date(), id: '2', updatedAt: new Date(), value: 20 };
24
+
25
+ describe('RedisEntityService', () => {
26
+ let dummyRepository: Partial<RedisRepositoryService<DummyEntity>>;
27
+ let dummyStore: Partial<RedisStoreService>;
28
+ let service: RedisEntityService<DummyEntity>;
29
+ beforeEach(() => {
30
+ dummyRepository = {
31
+ save: vi.fn(),
32
+ find: vi.fn()
33
+ };
34
+ dummyStore = {
35
+ createTransaction: vi.fn(),
36
+ endTransaction: vi.fn().mockResolvedValue(undefined)
37
+ };
38
+ service = new RedisEntityService(
39
+ dummyRepository as unknown as RedisRepositoryService<DummyEntity>,
40
+ dummyStore as unknown as RedisStoreService
41
+ );
42
+ });
43
+
44
+ describe('bulkCreate', () => {
45
+ it('returns repository.save result when no options provided', async () => {
46
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1, entity2]);
47
+ const res = await service.bulkCreate([entity1, entity2]);
48
+ expect(dummyRepository.save).toHaveBeenCalledWith([entity1, entity2], { transactionId: undefined });
49
+ expect(res).toEqual([entity1, entity2]);
50
+ });
51
+ it('returns repository.save result when transactionId provided', async () => {
52
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1, entity2]);
53
+ const opts: BulkCreateOptions = { transactionId: 'tx1' };
54
+ const res = await service.bulkCreate([entity1, entity2], opts);
55
+ expect(dummyRepository.save).toHaveBeenCalledWith([entity1, entity2], { transactionId: 'tx1' });
56
+ expect(res).toEqual([entity1, entity2]);
57
+ });
58
+ it('wraps call in transaction when forceTransaction is true and no transactionId', async () => {
59
+ (dummyStore.createTransaction as ReturnType<typeof vi.fn>).mockReturnValue('tx2');
60
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1]);
61
+ const opts: BulkCreateOptions = { forceTransaction: true };
62
+ const res = await service.bulkCreate([entity1], opts);
63
+ expect(dummyStore.createTransaction).toHaveBeenCalled();
64
+ expect(dummyRepository.save).toHaveBeenCalledWith([entity1], { transactionId: 'tx2' });
65
+ expect(dummyStore.endTransaction).toHaveBeenCalledWith('tx2');
66
+ expect(res).toEqual([entity1]);
67
+ });
68
+ });
69
+
70
+ describe('create', () => {
71
+ it('returns first element of repository.save when no options are provided', async () => {
72
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1]);
73
+ const res = await service.create(entity1);
74
+ expect(dummyRepository.save).toHaveBeenCalledWith(entity1, { transactionId: undefined });
75
+ expect(res).toEqual(entity1);
76
+ });
77
+ it('returns first element of repository.save when transactionId provided', async () => {
78
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1]);
79
+ const opts: CreateOptions = { transactionId: 'tx3' };
80
+ const res = await service.create(entity1, opts);
81
+ expect(dummyRepository.save).toHaveBeenCalledWith(entity1, { transactionId: 'tx3' });
82
+ expect(res).toEqual(entity1);
83
+ });
84
+ it('wraps call in transaction when forceTransaction is true and no transactionId', async () => {
85
+ (dummyStore.createTransaction as ReturnType<typeof vi.fn>).mockReturnValue('tx4');
86
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity1]);
87
+ const opts: CreateOptions = { forceTransaction: true };
88
+ const res = await service.create(entity1, opts);
89
+ expect(dummyStore.createTransaction).toHaveBeenCalled();
90
+ expect(dummyRepository.save).toHaveBeenCalledWith(entity1, { transactionId: 'tx4' });
91
+ expect(dummyStore.endTransaction).toHaveBeenCalledWith('tx4');
92
+ expect(res).toEqual(entity1);
93
+ });
94
+ });
95
+
96
+ describe('count', () => {
97
+ it('returns the length of repository.find result', async () => {
98
+ (dummyRepository.find as ReturnType<typeof vi.fn>).mockResolvedValue([entity1, entity2]);
99
+ const cnt = await service.count({ filters: {}, findAll: false });
100
+ expect(dummyRepository.find).toHaveBeenCalledWith({ filters: {}, findAll: false });
101
+ expect(cnt).toEqual(2);
102
+ });
103
+ });
104
+
105
+ describe('delete', () => {
106
+ it('wraps call in transaction when forceTransaction is true and no transactionId', async () => {
107
+ (dummyStore.createTransaction as ReturnType<typeof vi.fn>).mockReturnValue('tx5');
108
+ vi.spyOn(service, 'find').mockResolvedValue({ items: [entity1] } as DataFindResults<DummyEntity>);
109
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue(['key1']);
110
+ const opts: DeleteOptions = { filters: {}, forceTransaction: true };
111
+ const res = await service.delete(opts);
112
+ expect(dummyStore.createTransaction).toHaveBeenCalled();
113
+ expect(dummyRepository.save).toHaveBeenCalledWith([entity1], { delete: true, transactionId: 'tx5' });
114
+ expect(dummyStore.endTransaction).toHaveBeenCalledWith('tx5');
115
+ expect(res).toEqual({ count: 1 });
116
+ });
117
+ it('deletes normally when transactionId provided', async () => {
118
+ vi.spyOn(service, 'find').mockResolvedValue({ items: [entity1, entity2] } as DataFindResults<DummyEntity>);
119
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue(['k1', 'k2']);
120
+ const opts: DeleteOptions = { filters: {}, transactionId: 'tx6' };
121
+ const res = await service.delete(opts);
122
+ expect(dummyRepository.save).toHaveBeenCalledWith([entity1, entity2], { delete: true, transactionId: 'tx6' });
123
+ expect(res).toEqual({ count: 2 });
124
+ });
125
+ });
126
+
127
+ describe('find', () => {
128
+ it('parses page and perPage and sets "more" flag when items length equals perPage+1', async () => {
129
+ const items = new Array(11).fill(entity1);
130
+ (dummyRepository.find as ReturnType<typeof vi.fn>).mockResolvedValue(items);
131
+ const res = await service.find({ filters: {}, findAll: false, page: 2, perPage: 10 });
132
+ expect(dummyRepository.find).toHaveBeenCalledWith({ filters: {}, findAll: false, page: 2, perPage: 10 });
133
+ expect(res.page).toEqual(2);
134
+ expect(res.perPage).toEqual(10);
135
+ expect(res.more).toBe(true);
136
+ expect(res.items.length).toEqual(10);
137
+ });
138
+ it('sets perPage to items length when findAll is true', async () => {
139
+ (dummyRepository.find as ReturnType<typeof vi.fn>).mockResolvedValue([entity1, entity2]);
140
+ const res = await service.find({ filters: {}, findAll: true });
141
+ expect(dummyRepository.find).toHaveBeenCalledWith({ filters: {}, findAll: true, page: 1, perPage: 10 });
142
+ expect(res.perPage).toEqual(2);
143
+ expect(res.more).toBe(false);
144
+ });
145
+ });
146
+
147
+ describe('findOne', () => {
148
+ it('returns the first element when repository.find returns a non-empty array', async () => {
149
+ const expected = { id: '1', value: 1 } as unknown as DummyEntity;
150
+ (dummyRepository.find as ReturnType<typeof vi.fn>).mockResolvedValue([expected]);
151
+ const options: FindOneOptions = { filters: {} };
152
+ const result = await service.findOne(options);
153
+ expect(result).toEqual(expected);
154
+ });
155
+ it('returns null when repository.find returns an empty array', async () => {
156
+ (dummyRepository.find as ReturnType<typeof vi.fn>).mockResolvedValue([]);
157
+ const options: FindOneOptions = { filters: {} };
158
+ const result = await service.findOne(options);
159
+ expect(result).toBeNull();
160
+ });
161
+ });
162
+
163
+ describe('update', () => {
164
+ it('wraps call in transaction when forceTransaction is true and no transactionId', async () => {
165
+ (dummyStore.createTransaction as ReturnType<typeof vi.fn>).mockReturnValue('tx7');
166
+ vi.spyOn(service, 'findOne').mockResolvedValue(entity1);
167
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity2]);
168
+ const opts: UpdateOptions = { filters: {}, forceTransaction: true };
169
+ const res = await service.update(entity2, opts);
170
+ expect(dummyStore.createTransaction).toHaveBeenCalled();
171
+ expect(dummyRepository.save).toHaveBeenCalledWith(expect.any(Object), { transactionId: 'tx7' });
172
+ expect(dummyStore.endTransaction).toHaveBeenCalledWith('tx7');
173
+ expect(res).toEqual({ count: 1, items: [entity2] });
174
+ });
175
+ it('returns update result with count 0 when findOne returns null', async () => {
176
+ vi.spyOn(service, 'findOne').mockResolvedValue(null);
177
+ const opts: UpdateOptions = { filters: {}, transactionId: 'tx8' };
178
+ const res = await service.update(entity2, opts);
179
+ expect(res).toEqual({ count: 0, items: [] });
180
+ });
181
+ it('updates normally when findOne returns an item', async () => {
182
+ vi.spyOn(service, 'findOne').mockResolvedValue(entity1);
183
+ (dummyRepository.save as ReturnType<typeof vi.fn>).mockResolvedValue([entity2]);
184
+ const opts: UpdateOptions = { filters: {}, transactionId: 'tx9' };
185
+ const res = await service.update(entity2, opts);
186
+ expect(dummyRepository.save).toHaveBeenCalledWith(expect.any(Object), { transactionId: 'tx9' });
187
+ expect(res).toEqual({ count: 1, items: [entity2] });
188
+ });
189
+ });
190
+ });
@@ -0,0 +1,291 @@
1
+ import {
2
+ AppConfigCommonDataNoSQLEntityServiceSettings,
3
+ ApplicationError,
4
+ ConfigProviderService,
5
+ DataDeleteResult,
6
+ DataEntityService,
7
+ DataFindResults,
8
+ DataUpdateResult,
9
+ GenericObject,
10
+ ProcessObjectAllowedFieldsType
11
+ } from '@node-c/core';
12
+
13
+ import ld from 'lodash';
14
+
15
+ import {
16
+ BulkCreateOptions,
17
+ BulkCreatePrivateOptions,
18
+ CountOptions,
19
+ CountPrivateOptions,
20
+ CreateOptions,
21
+ CreatePrivateOptions,
22
+ DeleteOptions,
23
+ DeletePrivateOptions,
24
+ FindOneOptions,
25
+ FindOnePrivateOptions,
26
+ FindOptions,
27
+ FindPrivateOptions,
28
+ ServiceSaveOptions,
29
+ UpdateOptions,
30
+ UpdatePrivateOptions
31
+ } from './redis.entity.service.definitions';
32
+
33
+ import { RedisRepositoryService } from '../repository';
34
+ import { RedisStoreService } from '../store';
35
+
36
+ // TODO: support "pseudo-relations"
37
+ // TODO: support update of multiple items in the update method
38
+ export class RedisEntityService<Entity extends object> extends DataEntityService<Entity> {
39
+ protected settings: AppConfigCommonDataNoSQLEntityServiceSettings;
40
+
41
+ constructor(
42
+ // eslint-disable-next-line no-unused-vars
43
+ protected configProvider: ConfigProviderService,
44
+ // eslint-disable-next-line no-unused-vars
45
+ protected repository: RedisRepositoryService<Entity>,
46
+ // eslint-disable-next-line no-unused-vars
47
+ protected store: RedisStoreService
48
+ ) {
49
+ super(configProvider, repository.dataModuleName);
50
+ }
51
+
52
+ async bulkCreate(
53
+ data: Partial<Entity>[],
54
+ options?: BulkCreateOptions,
55
+ privateOptions?: BulkCreatePrivateOptions
56
+ ): Promise<Entity[]> {
57
+ const { store } = this;
58
+ const actualOptions = options || {};
59
+ const actualPrivateOptions = privateOptions || {};
60
+ const { forceTransaction, transactionId } = actualOptions;
61
+ if (!transactionId && forceTransaction) {
62
+ const tId = store.createTransaction();
63
+ const result = await this.bulkCreate(data, { ...actualOptions, transactionId: tId }, actualPrivateOptions);
64
+ await store.endTransaction(tId);
65
+ return result;
66
+ }
67
+ const { processInputAllowedFieldsEnabled, validate } = actualPrivateOptions;
68
+ return await this.save(data, {
69
+ generatePrimaryKeys: true,
70
+ processObjectAllowedFieldsEnabled: processInputAllowedFieldsEnabled,
71
+ transactionId,
72
+ validate
73
+ });
74
+ }
75
+
76
+ async count(options: CountOptions, privateOptions?: CountPrivateOptions): Promise<number | undefined> {
77
+ const { repository } = this;
78
+ const { filters, findAll } = options;
79
+ const { allowCountWithoutFilters, processFiltersAllowedFieldsEnabled } = privateOptions || {};
80
+ const parsedFilters = (await this.processObjectAllowedFields<GenericObject>(filters || {}, {
81
+ allowedFields: repository.columnNames,
82
+ isEnabled: processFiltersAllowedFieldsEnabled,
83
+ objectType: ProcessObjectAllowedFieldsType.Filters
84
+ })) as GenericObject;
85
+ if (!allowCountWithoutFilters && !Object.keys(parsedFilters).length) {
86
+ throw new ApplicationError('At least one filter field for counting is required.');
87
+ }
88
+ return (await repository.find({ filters: parsedFilters, findAll, individualSearch: false })).items.length;
89
+ }
90
+
91
+ async create(data: Partial<Entity>, options?: CreateOptions, privateOptions?: CreatePrivateOptions): Promise<Entity> {
92
+ const { store } = this;
93
+ const actualOptions = options || {};
94
+ const actualPrivateOptions = privateOptions || {};
95
+ const { forceTransaction, transactionId } = actualOptions;
96
+ if (!transactionId && forceTransaction) {
97
+ const tId = store.createTransaction();
98
+ const result = await this.create(data, { ...actualOptions, transactionId: tId }, actualPrivateOptions);
99
+ await store.endTransaction(tId);
100
+ return result;
101
+ }
102
+ const { processInputAllowedFieldsEnabled, validate } = actualPrivateOptions;
103
+ return await this.save<Partial<Entity>, Entity>(data instanceof Array ? data[0] : data, {
104
+ generatePrimaryKeys: false,
105
+ processObjectAllowedFieldsEnabled: processInputAllowedFieldsEnabled,
106
+ transactionId,
107
+ validate
108
+ });
109
+ }
110
+
111
+ async delete(options: DeleteOptions, privateOptions?: DeletePrivateOptions): Promise<DataDeleteResult<Entity>> {
112
+ const { repository, store } = this;
113
+ const { filters, forceTransaction, returnOriginalItems, transactionId } = options;
114
+ const actualPrivateOptions = privateOptions || {};
115
+ if (!transactionId && forceTransaction) {
116
+ const tId = store.createTransaction();
117
+ const result = await this.delete({ ...options, transactionId: tId }, actualPrivateOptions);
118
+ await store.endTransaction(tId);
119
+ return result;
120
+ }
121
+ const { processFiltersAllowedFieldsEnabled, requirePrimaryKeys = true } = actualPrivateOptions;
122
+ const parsedFilters = (await this.processObjectAllowedFields<GenericObject>(filters, {
123
+ allowedFields: repository.columnNames,
124
+ isEnabled: processFiltersAllowedFieldsEnabled,
125
+ objectType: ProcessObjectAllowedFieldsType.Filters
126
+ })) as GenericObject;
127
+ if (!Object.keys(parsedFilters).length) {
128
+ throw new ApplicationError('At least one filter field for deleting data is required.');
129
+ }
130
+ const { items: itemsToDelete } = await this.find({ filters, findAll: true }, { requirePrimaryKeys });
131
+ const results: string[] = await this.save(itemsToDelete, {
132
+ delete: true,
133
+ generatePrimaryKeys: false,
134
+ transactionId
135
+ });
136
+ const dataToReturn: DataDeleteResult<Entity> = { count: results.length };
137
+ if (returnOriginalItems) {
138
+ dataToReturn.originalItems = itemsToDelete;
139
+ }
140
+ return dataToReturn;
141
+ }
142
+
143
+ async find(options: FindOptions, privateOptions?: FindPrivateOptions): Promise<DataFindResults<Entity>> {
144
+ const { repository } = this;
145
+ const {
146
+ filters,
147
+ getTotalCount = true,
148
+ individualSearch,
149
+ page: optPage,
150
+ perPage: optPerPage,
151
+ findAll: optFindAll
152
+ } = options;
153
+ const { processFiltersAllowedFieldsEnabled, requirePrimaryKeys } = privateOptions || {};
154
+ // make sure it's truly a number - it could come as string from GET requests
155
+ const page = optPage ? parseInt(optPage as unknown as string, 10) : 1;
156
+ // same as above - must be a number
157
+ const perPage = optPerPage ? parseInt(optPerPage as unknown as string, 10) : 10;
158
+ const findAll = optFindAll === true || (optFindAll as unknown) === 'true';
159
+ const findResults: DataFindResults<Entity> = { page: 1, perPage: 0, items: [], more: false };
160
+ const parsedFilters = (await this.processObjectAllowedFields<GenericObject>(filters || {}, {
161
+ allowedFields: repository.columnNames,
162
+ isEnabled: processFiltersAllowedFieldsEnabled,
163
+ objectType: ProcessObjectAllowedFieldsType.Filters
164
+ })) as GenericObject;
165
+ if (!findAll) {
166
+ findResults.page = page;
167
+ findResults.perPage = perPage;
168
+ }
169
+ const { items, more } = await repository.find(
170
+ { filters: parsedFilters, findAll, individualSearch, page, perPage },
171
+ { requirePrimaryKeys }
172
+ );
173
+ if (findAll) {
174
+ findResults.perPage = items.length;
175
+ } else {
176
+ findResults.more = more;
177
+ if (getTotalCount) {
178
+ findResults.totalCount = await this.count(options, { allowCountWithoutFilters: true });
179
+ }
180
+ }
181
+ findResults.items = items;
182
+ return findResults;
183
+ }
184
+
185
+ async findOne(options: FindOneOptions, privateOptions?: FindOnePrivateOptions): Promise<Entity | null> {
186
+ const { filters } = options;
187
+ const { processFiltersAllowedFieldsEnabled, requirePrimaryKeys } = privateOptions || {};
188
+ const parsedFilters = (await this.processObjectAllowedFields<GenericObject>(filters, {
189
+ allowedFields: this.repository.columnNames,
190
+ isEnabled: processFiltersAllowedFieldsEnabled,
191
+ objectType: ProcessObjectAllowedFieldsType.Filters
192
+ })) as GenericObject;
193
+ if (!Object.keys(parsedFilters).length) {
194
+ throw new ApplicationError('At least one filter field is required for the findOne method.');
195
+ }
196
+ const result = await this.repository.find(
197
+ { filters, individualSearch: true, page: 1, perPage: 1 },
198
+ { requirePrimaryKeys }
199
+ );
200
+ return result.items[0] || null;
201
+ }
202
+
203
+ protected async save<Data extends Partial<Entity> | Partial<Entity>[], ReturnData = unknown>(
204
+ data: Data,
205
+ options: ServiceSaveOptions
206
+ ): Promise<ReturnData> {
207
+ const { repository, settings } = this;
208
+ const { validationSettings } = settings;
209
+ const {
210
+ delete: optDelete,
211
+ generatePrimaryKeys,
212
+ processObjectAllowedFieldsEnabled,
213
+ transactionId,
214
+ validate
215
+ } = options || {};
216
+ if (optDelete) {
217
+ return (await repository.save(data as unknown as Entity, {
218
+ delete: true,
219
+ generatePrimaryKeys: false,
220
+ transactionId,
221
+ validate: false
222
+ })) as ReturnData;
223
+ }
224
+ const dataToSave: Data | Data[] = await this.processObjectAllowedFields<Data>(data, {
225
+ allowedFields: repository.columnNames,
226
+ isEnabled: processObjectAllowedFieldsEnabled,
227
+ objectType: ProcessObjectAllowedFieldsType.Input
228
+ });
229
+ return (await repository.save(dataToSave as Entity, {
230
+ generatePrimaryKeys,
231
+ transactionId,
232
+ validate: typeof validate !== 'undefined' ? validate : !!validationSettings?.isEnabled
233
+ })) as ReturnData;
234
+ }
235
+
236
+ // TODO: reduce to need to double 2 finds (one here and one in the repository's save method)
237
+ // by refactoring both methods
238
+ async update(
239
+ data: Entity,
240
+ options: UpdateOptions,
241
+ privateOptions?: UpdatePrivateOptions
242
+ ): Promise<DataUpdateResult<Entity>> {
243
+ const { store } = this;
244
+ const { filters, forceTransaction, returnData, returnOriginalItems, transactionId } = options;
245
+ const actualPrivateOptions = privateOptions || {};
246
+ if (!transactionId && forceTransaction) {
247
+ const tId = store.createTransaction();
248
+ const result = await this.update(data, { ...options, transactionId: tId }, actualPrivateOptions);
249
+ await store.endTransaction(tId);
250
+ return result;
251
+ }
252
+ const {
253
+ processFiltersAllowedFieldsEnabled,
254
+ processInputAllowedFieldsEnabled,
255
+ requirePrimaryKeys = true,
256
+ validate
257
+ } = actualPrivateOptions;
258
+ const dataToReturn: DataUpdateResult<Entity> = {};
259
+ const { items: itemsToUpdate } = await this.find(
260
+ { filters, findAll: true },
261
+ { processFiltersAllowedFieldsEnabled, requirePrimaryKeys }
262
+ );
263
+ if (!itemsToUpdate.length) {
264
+ dataToReturn.count = 0;
265
+ if (returnData) {
266
+ dataToReturn.items = [];
267
+ }
268
+ if (returnOriginalItems) {
269
+ dataToReturn.originalItems = [];
270
+ }
271
+ return dataToReturn;
272
+ }
273
+ const updateResult = await this.save<Entity[], Entity[]>(
274
+ itemsToUpdate.map(item => ld.merge(item, data)),
275
+ {
276
+ generatePrimaryKeys: false,
277
+ processObjectAllowedFieldsEnabled: processInputAllowedFieldsEnabled,
278
+ transactionId,
279
+ validate
280
+ }
281
+ );
282
+ dataToReturn.count = updateResult.length;
283
+ if (returnData) {
284
+ dataToReturn.items = updateResult;
285
+ }
286
+ if (returnOriginalItems) {
287
+ dataToReturn.originalItems = itemsToUpdate;
288
+ }
289
+ return dataToReturn;
290
+ }
291
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './common/definitions';
2
+ export * from './entityService';
3
+ export * from './module';
4
+ export * from './repository';
5
+ export * from './store';
@@ -0,0 +1,2 @@
1
+ export * from './redis.module.definitions';
2
+ export * from './redis.module';
@@ -0,0 +1,18 @@
1
+ import { ModuleMetadata } from '@nestjs/common';
2
+
3
+ import { GenericObject } from '@node-c/core';
4
+
5
+ export interface RedisModuleOptions {
6
+ entityModuleRegisterOptions?: unknown;
7
+ exports?: ModuleMetadata['exports'];
8
+ folderData: GenericObject<unknown>;
9
+ imports?: {
10
+ atEnd?: ModuleMetadata['imports'];
11
+ postStore?: ModuleMetadata['imports'];
12
+ preStore?: ModuleMetadata['imports'];
13
+ };
14
+ moduleClass: unknown;
15
+ moduleName: string;
16
+ providers?: ModuleMetadata['providers'];
17
+ registerOptionsPerEntityModule?: GenericObject;
18
+ }
@@ -0,0 +1,80 @@
1
+ import { loadDynamicModules } from '@node-c/core';
2
+ import { Mock, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { RedisModule, RedisModuleOptions } from './index';
5
+
6
+ import { RedisStoreModule } from '../store';
7
+
8
+ vi.mock('@node-c/core', () => ({
9
+ loadDynamicModules: vi.fn()
10
+ }));
11
+ vi.mock('../store', () => ({
12
+ RedisStoreModule: {
13
+ register: vi.fn()
14
+ }
15
+ }));
16
+
17
+ describe('RedisModule', () => {
18
+ describe('register', () => {
19
+ const dummyModules = ['moduleA', 'moduleB'];
20
+ const dummyStoreDynamic = { module: 'StoreModule', global: false };
21
+ const dummyModuleClass = class TestModule {};
22
+ beforeEach(() => {
23
+ (loadDynamicModules as unknown as Mock).mockReturnValue({ modules: dummyModules });
24
+ (RedisStoreModule.register as unknown as Mock).mockReturnValue(dummyStoreDynamic);
25
+ });
26
+ it('should return dynamic module with all additionalImports and options provided', () => {
27
+ const additionalImports = {
28
+ preStore: ['pre1', 'pre2'],
29
+ postStore: ['post1'],
30
+ atEnd: ['end1']
31
+ };
32
+ const options = {
33
+ folderData: 'dummyFolder',
34
+ imports: additionalImports,
35
+ moduleClass: dummyModuleClass,
36
+ moduleName: 'testModule',
37
+ storeKey: 'store123',
38
+ providers: ['prov1'],
39
+ exports: ['exp1']
40
+ } as unknown as RedisModuleOptions;
41
+ const result = RedisModule.register(options);
42
+ expect(result.global).toBe(true);
43
+ expect(result.module).toBe(dummyModuleClass);
44
+ expect(result.imports).toEqual([
45
+ ...additionalImports.preStore,
46
+ dummyStoreDynamic,
47
+ ...additionalImports.postStore,
48
+ ...dummyModules,
49
+ ...additionalImports.atEnd
50
+ ]);
51
+ expect(result.providers).toEqual(['prov1']);
52
+ expect(result.exports).toEqual([...dummyModules, 'exp1']);
53
+ expect(RedisStoreModule.register).toHaveBeenCalledWith({
54
+ dataModuleName: options.moduleName
55
+ });
56
+ expect(loadDynamicModules).toHaveBeenCalledWith(options.folderData);
57
+ });
58
+ it('should return dynamic module when additionalImports, providers, and exports are undefined', () => {
59
+ const options = {
60
+ folderData: 'dummyFolder',
61
+ moduleClass: dummyModuleClass,
62
+ moduleName: 'testModule',
63
+ storeKey: 'store123'
64
+ } as unknown as RedisModuleOptions;
65
+ (loadDynamicModules as unknown as Mock).mockReturnValue({ modules: undefined });
66
+ const result = RedisModule.register(options);
67
+ expect(result.global).toBe(true);
68
+ expect(result.module).toBe(dummyModuleClass);
69
+ expect(result.imports).toEqual([
70
+ ...(undefined || []),
71
+ dummyStoreDynamic,
72
+ ...(undefined || []),
73
+ ...(undefined || []),
74
+ ...(undefined || [])
75
+ ]);
76
+ expect(result.providers).toEqual([]);
77
+ expect(result.exports).toEqual([...(undefined || []), ...(undefined || [])]);
78
+ });
79
+ });
80
+ });
@@ -0,0 +1,31 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+
3
+ import { loadDynamicModules } from '@node-c/core';
4
+
5
+ import { RedisModuleOptions } from './redis.module.definitions';
6
+
7
+ import { RedisStoreModule } from '../store';
8
+
9
+ export class RedisModule {
10
+ static register(options: RedisModuleOptions): DynamicModule {
11
+ const { folderData, imports: additionalImports, moduleClass, moduleName } = options;
12
+ const { atEnd: importsAtEnd, postStore: importsPostStore, preStore: importsPreStore } = additionalImports || {};
13
+ const { modules } = loadDynamicModules(folderData, {
14
+ moduleRegisterOptions: options.entityModuleRegisterOptions,
15
+ registerOptionsPerModule: options.registerOptionsPerEntityModule
16
+ });
17
+ return {
18
+ global: true,
19
+ module: moduleClass as DynamicModule['module'],
20
+ imports: [
21
+ ...(importsPreStore || []),
22
+ RedisStoreModule.register({ dataModuleName: moduleName }),
23
+ ...(importsPostStore || []),
24
+ ...(modules || []),
25
+ ...(importsAtEnd || [])
26
+ ],
27
+ providers: [...(options.providers || [])],
28
+ exports: [...(modules || []), ...(options.exports || [])]
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,3 @@
1
+ export * from './redis.repository.definitions';
2
+ export * from './redis.repository.module';
3
+ export * from './redis.repository.service';