@flusys/nestjs-form-builder 6.4.0 → 6.4.1

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.
@@ -189,6 +189,7 @@ let FormResultService = class FormResultService extends _classes.ApiService {
189
189
  const form = await this.getActiveForm(dto.formId);
190
190
  await this.validateSubmissionAccess(form, user, isPublic);
191
191
  const isDraft = dto.isDraft ?? false;
192
+ const staleResults = [];
192
193
  // Handle existing draft for authenticated users
193
194
  if (user?.id) {
194
195
  const existingDraft = await this.repository.findOne({
@@ -203,9 +204,13 @@ let FormResultService = class FormResultService extends _classes.ApiService {
203
204
  submittedAt: new Date()
204
205
  });
205
206
  const saved = await this.repository.save(existingDraft);
207
+ await this.invalidateResultCache([
208
+ saved
209
+ ]);
206
210
  return this.convertEntityToResponseDto(saved, false);
207
211
  }
208
212
  await this.repository.softRemove(existingDraft);
213
+ staleResults.push(existingDraft);
209
214
  }
210
215
  }
211
216
  const saved = await this.repository.save({
@@ -217,6 +222,10 @@ let FormResultService = class FormResultService extends _classes.ApiService {
217
222
  submittedAt: new Date(),
218
223
  isDraft
219
224
  });
225
+ await this.invalidateResultCache([
226
+ ...staleResults,
227
+ saved
228
+ ]);
220
229
  return this.convertEntityToResponseDto(saved, false);
221
230
  }
222
231
  async getMyDraft(formId, user) {
@@ -254,8 +263,17 @@ let FormResultService = class FormResultService extends _classes.ApiService {
254
263
  isDraft
255
264
  });
256
265
  const saved = await this.repository.save(draft);
266
+ await this.invalidateResultCache([
267
+ saved
268
+ ]);
257
269
  return this.convertEntityToResponseDto(saved, false);
258
270
  }
271
+ async invalidateResultCache(results) {
272
+ await Promise.all([
273
+ this.clearCacheForAll(),
274
+ this.clearCacheForId(results)
275
+ ]);
276
+ }
259
277
  async getByFormId(formId, user, pagination) {
260
278
  await this.ensureDataSourceRepository();
261
279
  const query = this.repository.createQueryBuilder(this.entityName).where('form_result.formId = :formId', {
@@ -108,6 +108,15 @@ describe('FormResultService', ()=>{
108
108
  let mockResultRepo;
109
109
  let mockFormRepo;
110
110
  let mockFormBuilderConfig;
111
+ const mockUtilsService = {
112
+ clearCache: jest.fn(),
113
+ getCacheKey: jest.fn().mockReturnValue('cache-key'),
114
+ trackCacheKey: jest.fn()
115
+ };
116
+ // Call history is shared across tests in this file; reset it so each cache assertion is exact.
117
+ beforeEach(()=>{
118
+ mockUtilsService.clearCache.mockClear();
119
+ });
111
120
  async function buildService(companyFeatureEnabled = false) {
112
121
  mockResultRepo = (0, _repositorymock.createMockRepository)();
113
122
  mockFormRepo = (0, _repositorymock.createMockRepository)();
@@ -138,7 +147,7 @@ describe('FormResultService', ()=>{
138
147
  },
139
148
  {
140
149
  provide: _modules.UtilsService,
141
- useValue: {}
150
+ useValue: mockUtilsService
142
151
  },
143
152
  {
144
153
  provide: _formbuilderconfigservice.FormBuilderConfigService,
@@ -417,6 +426,43 @@ describe('FormResultService', ()=>{
417
426
  isDraft: false
418
427
  }));
419
428
  });
429
+ it('invalidates the result list cache and the new entry after a submission', async ()=>{
430
+ await buildService(false);
431
+ mockFormRepo.findOne.mockResolvedValue(buildForm());
432
+ mockResultRepo.findOne.mockResolvedValue(null);
433
+ mockResultRepo.save.mockResolvedValue(buildFormResult({
434
+ id: 'result-1'
435
+ }));
436
+ await service.submitForm({
437
+ formId: 'form-uuid-1',
438
+ data: {
439
+ field1: 'x'
440
+ }
441
+ }, (0, _loggedusermock.buildMockUser)(), false);
442
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything());
443
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'result-1');
444
+ });
445
+ it('invalidates the soft-removed draft as well when a draft is finalized into a new record', async ()=>{
446
+ await buildService(false);
447
+ mockFormRepo.findOne.mockResolvedValue(buildForm());
448
+ mockResultRepo.findOne.mockResolvedValue(buildFormResult({
449
+ id: 'draft-1',
450
+ isDraft: true
451
+ }));
452
+ mockResultRepo.save.mockResolvedValue(buildFormResult({
453
+ id: 'result-2'
454
+ }));
455
+ await service.submitForm({
456
+ formId: 'form-uuid-1',
457
+ data: {
458
+ field1: 'x'
459
+ },
460
+ isDraft: false
461
+ }, (0, _loggedusermock.buildMockUser)(), false);
462
+ expect(mockResultRepo.softRemove).toHaveBeenCalled();
463
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'draft-1');
464
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'result-2');
465
+ });
420
466
  it('does not compute fields when saving as a draft', async ()=>{
421
467
  await buildService(false);
422
468
  const form = buildForm({
@@ -179,6 +179,7 @@ export class FormResultService extends ApiService {
179
179
  const form = await this.getActiveForm(dto.formId);
180
180
  await this.validateSubmissionAccess(form, user, isPublic);
181
181
  const isDraft = dto.isDraft ?? false;
182
+ const staleResults = [];
182
183
  // Handle existing draft for authenticated users
183
184
  if (user?.id) {
184
185
  const existingDraft = await this.repository.findOne({
@@ -193,9 +194,13 @@ export class FormResultService extends ApiService {
193
194
  submittedAt: new Date()
194
195
  });
195
196
  const saved = await this.repository.save(existingDraft);
197
+ await this.invalidateResultCache([
198
+ saved
199
+ ]);
196
200
  return this.convertEntityToResponseDto(saved, false);
197
201
  }
198
202
  await this.repository.softRemove(existingDraft);
203
+ staleResults.push(existingDraft);
199
204
  }
200
205
  }
201
206
  const saved = await this.repository.save({
@@ -207,6 +212,10 @@ export class FormResultService extends ApiService {
207
212
  submittedAt: new Date(),
208
213
  isDraft
209
214
  });
215
+ await this.invalidateResultCache([
216
+ ...staleResults,
217
+ saved
218
+ ]);
210
219
  return this.convertEntityToResponseDto(saved, false);
211
220
  }
212
221
  async getMyDraft(formId, user) {
@@ -244,8 +253,17 @@ export class FormResultService extends ApiService {
244
253
  isDraft
245
254
  });
246
255
  const saved = await this.repository.save(draft);
256
+ await this.invalidateResultCache([
257
+ saved
258
+ ]);
247
259
  return this.convertEntityToResponseDto(saved, false);
248
260
  }
261
+ async invalidateResultCache(results) {
262
+ await Promise.all([
263
+ this.clearCacheForAll(),
264
+ this.clearCacheForId(results)
265
+ ]);
266
+ }
249
267
  async getByFormId(formId, user, pagination) {
250
268
  await this.ensureDataSourceRepository();
251
269
  const query = this.repository.createQueryBuilder(this.entityName).where('form_result.formId = :formId', {
@@ -63,6 +63,15 @@ describe('FormResultService', ()=>{
63
63
  let mockResultRepo;
64
64
  let mockFormRepo;
65
65
  let mockFormBuilderConfig;
66
+ const mockUtilsService = {
67
+ clearCache: jest.fn(),
68
+ getCacheKey: jest.fn().mockReturnValue('cache-key'),
69
+ trackCacheKey: jest.fn()
70
+ };
71
+ // Call history is shared across tests in this file; reset it so each cache assertion is exact.
72
+ beforeEach(()=>{
73
+ mockUtilsService.clearCache.mockClear();
74
+ });
66
75
  async function buildService(companyFeatureEnabled = false) {
67
76
  mockResultRepo = createMockRepository();
68
77
  mockFormRepo = createMockRepository();
@@ -93,7 +102,7 @@ describe('FormResultService', ()=>{
93
102
  },
94
103
  {
95
104
  provide: UtilsService,
96
- useValue: {}
105
+ useValue: mockUtilsService
97
106
  },
98
107
  {
99
108
  provide: FormBuilderConfigService,
@@ -372,6 +381,43 @@ describe('FormResultService', ()=>{
372
381
  isDraft: false
373
382
  }));
374
383
  });
384
+ it('invalidates the result list cache and the new entry after a submission', async ()=>{
385
+ await buildService(false);
386
+ mockFormRepo.findOne.mockResolvedValue(buildForm());
387
+ mockResultRepo.findOne.mockResolvedValue(null);
388
+ mockResultRepo.save.mockResolvedValue(buildFormResult({
389
+ id: 'result-1'
390
+ }));
391
+ await service.submitForm({
392
+ formId: 'form-uuid-1',
393
+ data: {
394
+ field1: 'x'
395
+ }
396
+ }, buildMockUser(), false);
397
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything());
398
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'result-1');
399
+ });
400
+ it('invalidates the soft-removed draft as well when a draft is finalized into a new record', async ()=>{
401
+ await buildService(false);
402
+ mockFormRepo.findOne.mockResolvedValue(buildForm());
403
+ mockResultRepo.findOne.mockResolvedValue(buildFormResult({
404
+ id: 'draft-1',
405
+ isDraft: true
406
+ }));
407
+ mockResultRepo.save.mockResolvedValue(buildFormResult({
408
+ id: 'result-2'
409
+ }));
410
+ await service.submitForm({
411
+ formId: 'form-uuid-1',
412
+ data: {
413
+ field1: 'x'
414
+ },
415
+ isDraft: false
416
+ }, buildMockUser(), false);
417
+ expect(mockResultRepo.softRemove).toHaveBeenCalled();
418
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'draft-1');
419
+ expect(mockUtilsService.clearCache).toHaveBeenCalledWith('form_result', expect.anything(), 'result-2');
420
+ });
375
421
  it('does not compute fields when saving as a draft', async ()=>{
376
422
  await buildService(false);
377
423
  const form = buildForm({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flusys/nestjs-form-builder",
3
- "version": "6.4.0",
3
+ "version": "6.4.1",
4
4
  "description": "Dynamic form builder module with schema versioning and access control",
5
5
  "main": "cjs/index.js",
6
6
  "module": "fesm/index.js",
@@ -84,7 +84,7 @@
84
84
  "express": "^5.0.0"
85
85
  },
86
86
  "dependencies": {
87
- "@flusys/nestjs-core": "6.4.0",
88
- "@flusys/nestjs-shared": "6.4.0"
87
+ "@flusys/nestjs-core": "6.4.1",
88
+ "@flusys/nestjs-shared": "6.4.1"
89
89
  }
90
90
  }
@@ -33,6 +33,7 @@ export declare class FormResultService extends ApiService<CreateFormResultDto, U
33
33
  submitForm(dto: SubmitFormDto, user: ILoggedUserInfo | null, isPublic?: boolean): Promise<IFormResult>;
34
34
  getMyDraft(formId: string, user: ILoggedUserInfo): Promise<IFormResult | null>;
35
35
  updateDraft(draftId: string, dto: SubmitFormDto, user: ILoggedUserInfo): Promise<IFormResult>;
36
+ private invalidateResultCache;
36
37
  getByFormId(formId: string, user: ILoggedUserInfo | null, pagination?: {
37
38
  page?: number;
38
39
  pageSize?: number;