@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.
|
|
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.
|
|
88
|
-
"@flusys/nestjs-shared": "6.4.
|
|
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;
|