@vroskus/library-datahandler 1.0.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.
package/src/index.ts ADDED
@@ -0,0 +1,947 @@
1
+ // Helpers
2
+ import {
3
+ Op,
4
+ Sequelize,
5
+ } from 'sequelize';
6
+ import _ from 'lodash';
7
+ import pluralize from 'pluralize';
8
+ import {
9
+ baseErrorKey,
10
+ CustomError,
11
+ } from '@vroskus/library-error';
12
+ import helpers from './helpers';
13
+
14
+ // Types
15
+ import type {
16
+ $Config,
17
+ $Include,
18
+ $QueryParams,
19
+ $Where,
20
+ } from './types';
21
+
22
+ import type {
23
+ $Helpers,
24
+ } from './helpers';
25
+
26
+ export * from './types';
27
+
28
+ class DatabaseService<C extends $Config, MC extends {
29
+ Config: any;
30
+ Models: any;
31
+ Classes: any;
32
+ Shapes: any;
33
+ }> {
34
+ models: MC['Classes'];
35
+
36
+ helpers: $Helpers;
37
+
38
+ stack: Sequelize;
39
+
40
+ Op: object;
41
+
42
+ constructor({
43
+ database,
44
+ dialect,
45
+ host,
46
+ logging,
47
+ password,
48
+ port,
49
+ storage,
50
+ username,
51
+ }: C, modelShapes: MC['Shapes']) {
52
+ const dbConfig = {
53
+ database,
54
+ define: {
55
+ charset: 'utf8',
56
+ dialectOptions: {
57
+ collate: 'utf8_general_ci',
58
+ },
59
+ timestamps: true,
60
+ },
61
+ dialect: dialect || 'mysql',
62
+ dialectOptions: {
63
+ },
64
+ host,
65
+ logging,
66
+ password,
67
+ port: Number(port),
68
+ storage,
69
+ username,
70
+ };
71
+
72
+ const stack = new Sequelize(
73
+ dbConfig.database,
74
+ dbConfig.username,
75
+ dbConfig.password,
76
+ dbConfig,
77
+ );
78
+
79
+ // Init models
80
+ const models: MC['Classes'] = _.mapValues(
81
+ modelShapes,
82
+ (f) => f(
83
+ Sequelize,
84
+ stack,
85
+ helpers,
86
+ ),
87
+ );
88
+
89
+ // Init models associations
90
+ Object.keys(models).forEach((modelName: keyof MC['Classes']) => {
91
+ if (models[modelName].associate) {
92
+ models[modelName].associate(models);
93
+ }
94
+
95
+ if (models[modelName].hooks) {
96
+ models[modelName].hooks(models);
97
+ }
98
+ });
99
+
100
+ this.models = models;
101
+
102
+ this.helpers = helpers;
103
+
104
+ this.stack = stack;
105
+
106
+ this.Op = Op;
107
+ }
108
+
109
+ async setupTestEnvironment(): Promise<void> {
110
+ await this.stack.sync({
111
+ force: true,
112
+ });
113
+ }
114
+
115
+ // getModel private method
116
+ #getModel<MN extends keyof MC['Config']>({
117
+ modelName,
118
+ }: {
119
+ modelName: MN;
120
+ }): MC['Classes'][MN] {
121
+ if (!_.has(
122
+ this.models,
123
+ modelName,
124
+ )) {
125
+ throw new CustomError(
126
+ 'Invalid model name',
127
+ baseErrorKey.invalidModelNameError,
128
+ {
129
+ data: {
130
+ modelName,
131
+ },
132
+ },
133
+ );
134
+ }
135
+
136
+ return _.get(
137
+ this.models,
138
+ modelName,
139
+ );
140
+ }
141
+
142
+ // getModelInstance private method
143
+ async #getModelInstance<MN extends keyof MC['Config']>({
144
+ id,
145
+ modelName,
146
+ }: {
147
+ modelName: MN;
148
+ id: string;
149
+ }): Promise<MC['Models'][MN]> {
150
+ const model = this.#getModel({
151
+ modelName,
152
+ });
153
+
154
+ const modelInstance = await model.findByPk(id);
155
+
156
+ if (modelInstance === null) {
157
+ throw new CustomError(
158
+ 'Record was not found',
159
+ baseErrorKey.entityNotFoundError,
160
+ {
161
+ data: {
162
+ id,
163
+ modelName,
164
+ },
165
+ },
166
+ );
167
+ }
168
+
169
+ return modelInstance;
170
+ }
171
+
172
+ // mapQueryAssociations private method
173
+ #mapQueryAssociations<MN extends keyof MC['Config']>({
174
+ include: includeInput,
175
+ where: whereInput,
176
+ }: {
177
+ where: $Where<MC['Config'], MN>;
178
+ include: $Include<MC['Config'], MC['Classes'], MN>;
179
+ }): {
180
+ where: $Where<MC['Config'], MN>;
181
+ as?: string;
182
+ include: $Include<MC['Config'], MC['Classes'], MN>;
183
+ } {
184
+ let where = _.clone(whereInput);
185
+
186
+ const include = _.clone(includeInput);
187
+
188
+ _.forOwn(
189
+ where,
190
+ (value: string, key: string) => {
191
+ if (key.includes('.')) {
192
+ const associationName = key.substr(
193
+ 0,
194
+ key.indexOf('.'),
195
+ );
196
+ const param = key.substr(key.indexOf('.') + 1);
197
+
198
+ const model = this.models[pluralize.singular(associationName)];
199
+
200
+ if (model) {
201
+ include.push({
202
+ as: associationName,
203
+ model,
204
+ ...this.#mapQueryAssociations({
205
+ include: [],
206
+ where: {
207
+ [param]: value,
208
+ },
209
+ }),
210
+ });
211
+ } else {
212
+ include.push(associationName);
213
+ }
214
+
215
+ where = _.omit(
216
+ where,
217
+ [key],
218
+ );
219
+ }
220
+ },
221
+ );
222
+
223
+ return {
224
+ include,
225
+ where,
226
+ };
227
+ }
228
+
229
+ // prepareQueryParams private method
230
+ #prepareQueryParams<MN extends keyof MC['Config']>({
231
+ params,
232
+ }: {
233
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
234
+ }): $QueryParams<MC['Config'], MC['Classes'], MN> {
235
+ const preparedParams = {
236
+ attributes: params.attributes,
237
+ include: params.include,
238
+ where: undefined,
239
+ };
240
+
241
+ if (params.where) {
242
+ const {
243
+ include,
244
+ where,
245
+ } = this.#mapQueryAssociations({
246
+ include: _.get(
247
+ preparedParams,
248
+ 'include',
249
+ [],
250
+ ),
251
+ where: params.where,
252
+ });
253
+
254
+ preparedParams.where = where;
255
+ preparedParams.include = include;
256
+ }
257
+
258
+ return preparedParams;
259
+ }
260
+
261
+ // associate private method
262
+ async #associate({
263
+ action,
264
+ associationModelId,
265
+ associationModelName,
266
+ id,
267
+ modelName,
268
+ pivot,
269
+ }: {
270
+ action: 'add' | 'remove';
271
+ modelName: keyof MC['Config'];
272
+ id: string;
273
+ associationModelName: keyof MC['Config'];
274
+ associationModelId: string;
275
+ pivot?: object;
276
+ }) {
277
+ const actionMethod = `${action}${String(associationModelName)}`;
278
+
279
+ const modelInstance = await this.#getModelInstance({
280
+ id,
281
+ modelName,
282
+ });
283
+
284
+ const associationModel = await this.#getModelInstance({
285
+ id: associationModelId,
286
+ modelName: associationModelName,
287
+ });
288
+
289
+ const pivotData = pivot ? {
290
+ through: pivot,
291
+ } : {
292
+ };
293
+
294
+ if (typeof modelInstance[actionMethod] !== 'function') {
295
+ throw new CustomError(
296
+ 'Association action method not found',
297
+ baseErrorKey.associationActionMethodNotFoundError,
298
+ {
299
+ data: {
300
+ action,
301
+ associationModelId,
302
+ associationModelName,
303
+ id,
304
+ modelName,
305
+ pivot,
306
+ },
307
+ },
308
+ );
309
+ }
310
+
311
+ return modelInstance[actionMethod](
312
+ associationModel,
313
+ pivotData,
314
+ );
315
+ }
316
+
317
+ // getOne method
318
+ async getOne<MN extends keyof MC['Config']>({
319
+ modelName,
320
+ params,
321
+ }: {
322
+ modelName: MN;
323
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
324
+ }): Promise<MC['Models'][MN] | null> {
325
+ const model = this.#getModel({
326
+ modelName,
327
+ });
328
+ const request = this.#prepareQueryParams({
329
+ params,
330
+ });
331
+ const modelInstances = await model.findAll(request);
332
+
333
+ // If more then one record found
334
+ if (modelInstances.length > 1) {
335
+ throw new CustomError(
336
+ 'Multiple records found',
337
+ baseErrorKey.multipleRecordsFoundError,
338
+ {
339
+ data: {
340
+ amount: modelInstances.length,
341
+ modelName,
342
+ request,
343
+ },
344
+ },
345
+ );
346
+ }
347
+
348
+ return _.head(modelInstances) || null;
349
+ }
350
+
351
+ // getFirst method
352
+ async getFirst<MN extends keyof MC['Config']>({
353
+ modelName,
354
+ params,
355
+ }: {
356
+ modelName: MN;
357
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
358
+ }): Promise<MC['Models'][MN] | null> {
359
+ const model = this.#getModel({
360
+ modelName,
361
+ });
362
+ const request = this.#prepareQueryParams({
363
+ params,
364
+ });
365
+ const modelInstances = await model.findAll(request);
366
+
367
+ return _.first(modelInstances) || null;
368
+ }
369
+
370
+ // getLast method
371
+ async getLast<MN extends keyof MC['Config']>({
372
+ modelName,
373
+ params,
374
+ }: {
375
+ modelName: MN;
376
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
377
+ }): Promise<MC['Models'][MN] | null> {
378
+ const model = this.#getModel({
379
+ modelName,
380
+ });
381
+ const request = this.#prepareQueryParams({
382
+ params,
383
+ });
384
+ const modelInstances = await model.findAll(request);
385
+
386
+ return _.last(modelInstances) || null;
387
+ }
388
+
389
+ // getMany method
390
+ async getMany<MN extends keyof MC['Config']>({
391
+ modelName,
392
+ params,
393
+ }: {
394
+ modelName: MN;
395
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
396
+ }): Promise<Array<MC['Models'][MN]>> {
397
+ const model = this.#getModel({
398
+ modelName,
399
+ });
400
+
401
+ const request = this.#prepareQueryParams({
402
+ params,
403
+ });
404
+
405
+ return model.findAll(request);
406
+ }
407
+
408
+ // createOne method
409
+ async createOne<MN extends keyof MC['Config']>({
410
+ modelName,
411
+ params,
412
+ }: {
413
+ modelName: MN;
414
+ params: MC['Config'][MN]['ModelCreateParams'];
415
+ }): Promise<MC['Models'][MN]> {
416
+ const model = this.#getModel({
417
+ modelName,
418
+ });
419
+
420
+ const attributes = _.clone(params);
421
+
422
+ return model.create(attributes);
423
+ }
424
+
425
+ // createBundle method
426
+ async createBundle<MN extends keyof MC['Config']>({
427
+ include,
428
+ modelName,
429
+ params,
430
+ }: {
431
+ modelName: MN;
432
+ params: MC['Config'][MN]['ModelCreateParams'];
433
+ include: $Include<MC['Config'], MC['Classes'], MN>;
434
+ }): Promise<MC['Models'][MN]> {
435
+ const model = this.#getModel({
436
+ modelName,
437
+ });
438
+
439
+ const attributes = _.clone(params);
440
+
441
+ return model.create(
442
+ attributes,
443
+ {
444
+ include,
445
+ },
446
+ );
447
+ }
448
+
449
+ // createMany method
450
+ async createMany<MN extends keyof MC['Config']>({
451
+ modelName,
452
+ params,
453
+ }: {
454
+ modelName: MN;
455
+ params: Array<MC['Config'][MN]['ModelCreateParams']>;
456
+ }): Promise<Array<MC['Models'][MN]>> {
457
+ const model = this.#getModel({
458
+ modelName,
459
+ });
460
+
461
+ const attributes = _.clone(params);
462
+
463
+ return model.bulkCreate(attributes);
464
+ }
465
+
466
+ // upsertOne method
467
+ async upsertOne<MN extends keyof MC['Config']>({
468
+ modelName,
469
+ params,
470
+ where,
471
+ }: {
472
+ modelName: MN;
473
+ params: MC['Config'][MN]['ModelUpsertParams'] | MC['Models'][MN];
474
+ where: $Where<MC['Config'], MN>;
475
+ }): Promise<MC['Models'][MN]> {
476
+ const attributes = _.omit(
477
+ params,
478
+ ['id', 'createdAt', 'updatedAt'],
479
+ );
480
+
481
+ const model = this.#getModel({
482
+ modelName,
483
+ });
484
+
485
+ const request = this.#prepareQueryParams({
486
+ params: {
487
+ where,
488
+ },
489
+ });
490
+ const modelInstances = await model.findAll(request);
491
+
492
+ if (modelInstances.length > 1) {
493
+ throw new CustomError(
494
+ 'Multiple records found',
495
+ baseErrorKey.multipleRecordsFoundError,
496
+ {
497
+ data: {
498
+ amount: modelInstances.length,
499
+ modelName,
500
+ request,
501
+ },
502
+ },
503
+ );
504
+ }
505
+
506
+ if (modelInstances.length === 0) {
507
+ return model.create(attributes);
508
+ }
509
+
510
+ if (typeof modelInstances[0].update === 'function') {
511
+ return modelInstances[0].update(attributes);
512
+ }
513
+
514
+ return modelInstances[0];
515
+ }
516
+
517
+ // updateOne method
518
+ async updateOne<MN extends keyof MC['Config']>({
519
+ modelName,
520
+ params,
521
+ }: {
522
+ modelName: MN;
523
+ params: MC['Config'][MN]['ModelUpdateParams'] | MC['Models'][MN];
524
+ }): Promise<MC['Models'][MN]> {
525
+ const {
526
+ // @ts-ignore
527
+ id,
528
+ } = params;
529
+
530
+ const modelInstance = await this.#getModelInstance({
531
+ id,
532
+ modelName,
533
+ });
534
+
535
+ const attributes: MC['Config'][MN]['ModelUpdateParams'] = _.omit(
536
+ params,
537
+ ['id', 'createdAt', 'updatedAt'],
538
+ );
539
+
540
+ if (typeof modelInstance.update === 'function') {
541
+ await modelInstance.update(attributes);
542
+ }
543
+
544
+ return modelInstance;
545
+ }
546
+
547
+ // toggleOne method
548
+ async toggleOne<MN extends keyof MC['Config']>({
549
+ modelName,
550
+ where,
551
+ }: {
552
+ modelName: MN;
553
+ where: $Where<MC['Config'], MN>;
554
+ }): Promise<MC['Models'][MN]> {
555
+ const attributes = _.omit(
556
+ where,
557
+ ['id', 'createdAt', 'updatedAt'],
558
+ );
559
+
560
+ const model = this.#getModel({
561
+ modelName,
562
+ });
563
+
564
+ const request = this.#prepareQueryParams({
565
+ params: {
566
+ where,
567
+ },
568
+ });
569
+ let modelInstance = await model.findOne(request);
570
+
571
+ if (modelInstance !== null) {
572
+ await modelInstance.destroy();
573
+ } else {
574
+ modelInstance = await model.create(attributes);
575
+ }
576
+
577
+ return modelInstance;
578
+ }
579
+
580
+ // deleteOne method
581
+ async deleteOne<MN extends keyof MC['Config']>({
582
+ modelName,
583
+ params,
584
+ }: {
585
+ modelName: MN;
586
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
587
+ }): Promise<MC['Models'][MN]> {
588
+ const model = this.#getModel({
589
+ modelName,
590
+ });
591
+ const request = this.#prepareQueryParams({
592
+ params,
593
+ });
594
+ const modelInstances = await model.findAll(request);
595
+
596
+ // If more then one record found
597
+ if (modelInstances.length > 1) {
598
+ throw new CustomError(
599
+ 'Multiple records found',
600
+ baseErrorKey.multipleRecordsFoundError,
601
+ {
602
+ data: {
603
+ amount: modelInstances.length,
604
+ modelName,
605
+ request,
606
+ },
607
+ },
608
+ );
609
+ }
610
+
611
+ if (modelInstances.length === 0) {
612
+ throw new CustomError(
613
+ 'Record was not found',
614
+ baseErrorKey.entityNotFoundError,
615
+ {
616
+ data: {
617
+ modelName,
618
+ request,
619
+ },
620
+ },
621
+ );
622
+ }
623
+
624
+ await modelInstances[0].destroy();
625
+
626
+ return modelInstances[0];
627
+ }
628
+
629
+ // deleteMany method
630
+ async deleteMany<MN extends keyof MC['Config']>({
631
+ modelName,
632
+ params,
633
+ }: {
634
+ modelName: MN;
635
+ params: $QueryParams<MC['Config'], MC['Classes'], MN>;
636
+ }): Promise<Array<MC['Models'][MN]>> {
637
+ const model = this.#getModel({
638
+ modelName,
639
+ });
640
+ const request = this.#prepareQueryParams({
641
+ params,
642
+ });
643
+ const modelInstances = await model.findAll(request);
644
+
645
+ for (let index = 0; index < modelInstances.length; index += 1) {
646
+ if (modelInstances[index].destroy) {
647
+ await modelInstances[index].destroy();
648
+ }
649
+ }
650
+
651
+ return modelInstances;
652
+ }
653
+
654
+ // sync method
655
+ async sync<MN extends keyof MC['Config'], AM extends keyof MC['Config']>({
656
+ modelName,
657
+ params: {
658
+ associationModelId,
659
+ associationModelName,
660
+ items,
661
+ },
662
+ }: {
663
+ modelName: MN;
664
+ params: {
665
+ items: Array<MC['Config'][MN]['ModelUpsertParams']>;
666
+ associationModelName: AM;
667
+ associationModelId: string;
668
+ };
669
+ }): Promise<{
670
+ createdItemIds: Array<string>;
671
+ deletedItemIds: Array<string>;
672
+ syncedItems: Array<MC['Models'][MN]>;
673
+ updatedItemIds: Array<string>;
674
+ }> {
675
+ const actions: Array<Promise<MC['Models'][MN]>> = [];
676
+ const where = {
677
+ [`${String(associationModelName)}Id`]: associationModelId,
678
+ };
679
+
680
+ const model = this.#getModel({
681
+ modelName,
682
+ });
683
+
684
+ this.#getModel({
685
+ modelName: associationModelName,
686
+ });
687
+
688
+ const presentItems = await model.findAll({
689
+ where,
690
+ });
691
+
692
+ const presentItemIds: Array<string> = _.map(
693
+ presentItems,
694
+ 'id',
695
+ );
696
+
697
+ const updatedItemIds: Array<string> = _.map(
698
+ _.filter(
699
+ items,
700
+ (item) => item.id,
701
+ ),
702
+ 'id',
703
+ );
704
+
705
+ const deletedItemIds: Array<string> = _.without(
706
+ presentItemIds,
707
+ ...updatedItemIds,
708
+ );
709
+
710
+ const createdItemIds: Array<string> = [];
711
+ const syncedItems: Array<MC['Models'][MN]> = [];
712
+
713
+ items.forEach((item) => {
714
+ const request = {
715
+ modelName,
716
+ params: {
717
+ ...item,
718
+ ...where,
719
+ },
720
+ where: {
721
+ id: item.id || null,
722
+ },
723
+ };
724
+
725
+ const action = (async () => {
726
+ const upsertedItem = await this.upsertOne(request);
727
+
728
+ if (!item.id) {
729
+ createdItemIds.push(upsertedItem.id);
730
+ }
731
+
732
+ syncedItems.push(upsertedItem);
733
+ })();
734
+
735
+ actions.push(action);
736
+ });
737
+
738
+ deletedItemIds.forEach((id: string) => {
739
+ const request = {
740
+ modelName,
741
+ params: {
742
+ where: {
743
+ id,
744
+ ...where,
745
+ },
746
+ },
747
+ };
748
+
749
+ const action = this.deleteOne(request);
750
+
751
+ actions.push(action);
752
+ });
753
+
754
+ await Promise.all(actions);
755
+
756
+ return {
757
+ createdItemIds,
758
+ deletedItemIds,
759
+ syncedItems,
760
+ updatedItemIds,
761
+ };
762
+ }
763
+
764
+ // addAssociation method
765
+ async addAssociation<MN extends keyof MC['Config'], AM extends keyof MC['Config']>({
766
+ modelName,
767
+ params,
768
+ }: {
769
+ modelName: MN;
770
+ params: {
771
+ id: string;
772
+ associationModelName: AM;
773
+ associationModelId: string;
774
+ pivot?: object;
775
+ };
776
+ }): Promise<MC['Models'][MN]> {
777
+ const {
778
+ associationModelId,
779
+ associationModelName,
780
+ id,
781
+ pivot,
782
+ } = params;
783
+
784
+ const result = await this.#associate({
785
+ action: 'add',
786
+ associationModelId,
787
+ associationModelName,
788
+ id,
789
+ modelName,
790
+ pivot,
791
+ });
792
+
793
+ if (!result || (result && result.length !== 1)) {
794
+ throw new CustomError(
795
+ 'Already associatied records',
796
+ baseErrorKey.alreadyAssociatedRecordsError,
797
+ {
798
+ data: {
799
+ model1: modelName,
800
+ model1id: id,
801
+ model2: associationModelName,
802
+ model2id: associationModelId,
803
+ },
804
+ },
805
+ );
806
+ }
807
+
808
+ return result[0];
809
+ }
810
+
811
+ // removeAssociation method
812
+ async removeAssociation<MN extends keyof MC['Config'], AM extends keyof MC['Config']>({
813
+ modelName,
814
+ params,
815
+ }: {
816
+ modelName: MN;
817
+ params: {
818
+ id: string;
819
+ associationModelName: AM;
820
+ associationModelId: string;
821
+ pivot?: object;
822
+ };
823
+ }): Promise<boolean> {
824
+ const {
825
+ associationModelId,
826
+ associationModelName,
827
+ id,
828
+ } = params;
829
+
830
+ const result = await this.#associate({
831
+ action: 'remove',
832
+ associationModelId,
833
+ associationModelName,
834
+ id,
835
+ modelName,
836
+ });
837
+
838
+ if (!result || (result && result !== 1)) {
839
+ throw new CustomError(
840
+ 'Not associatied records',
841
+ baseErrorKey.notAssociatedRecordsError,
842
+ {
843
+ data: {
844
+ model1: modelName,
845
+ model1id: id,
846
+ model2: associationModelName,
847
+ model2id: associationModelId,
848
+ },
849
+ },
850
+ );
851
+ }
852
+
853
+ return true;
854
+ }
855
+
856
+ // syncAssociations method
857
+ async syncAssociations<MN extends keyof MC['Config'], AM extends keyof MC['Config']>({
858
+ modelName,
859
+ params: {
860
+ associationModelIds,
861
+ associationModelName,
862
+ id,
863
+ },
864
+ }: {
865
+ modelName: MN;
866
+ params: {
867
+ id: string;
868
+ associationModelName: AM;
869
+ associationModelIds: Array<string>;
870
+ };
871
+ }): Promise<{
872
+ addedAssociationModelIds: Array<string>;
873
+ removedAssociationModelIds: Array<string>;
874
+ }> {
875
+ const actions: Array<Promise<MC['Models'][MN]>> = [];
876
+ const pluralAssociationModelName = pluralize.plural(associationModelName);
877
+
878
+ const model = this.#getModel({
879
+ modelName,
880
+ });
881
+
882
+ const modelInstance = await model.findOne({
883
+ include: [pluralAssociationModelName],
884
+ where: {
885
+ id,
886
+ },
887
+ });
888
+
889
+ const presentAssociationModelIds: Array<string> = _.map(
890
+ _.get(
891
+ modelInstance,
892
+ pluralAssociationModelName,
893
+ [],
894
+ ),
895
+ 'id',
896
+ );
897
+
898
+ const addAssociationModelIds = _.without(
899
+ associationModelIds,
900
+ ...presentAssociationModelIds,
901
+ );
902
+
903
+ addAssociationModelIds.forEach((associationModelId: string) => {
904
+ const addAssociationRequest = {
905
+ modelName,
906
+ params: {
907
+ associationModelId,
908
+ associationModelName,
909
+ id,
910
+ },
911
+ };
912
+
913
+ const action = this.addAssociation(addAssociationRequest);
914
+
915
+ actions.push(action);
916
+ });
917
+
918
+ const removeAssociationModelIds = _.without(
919
+ presentAssociationModelIds,
920
+ ...associationModelIds,
921
+ );
922
+
923
+ removeAssociationModelIds.forEach((associationModelId: string) => {
924
+ const addAssociationRequest = {
925
+ modelName,
926
+ params: {
927
+ associationModelId,
928
+ associationModelName,
929
+ id,
930
+ },
931
+ };
932
+
933
+ const action = this.removeAssociation(addAssociationRequest);
934
+
935
+ actions.push(action);
936
+ });
937
+
938
+ await Promise.all(actions);
939
+
940
+ return {
941
+ addedAssociationModelIds: addAssociationModelIds,
942
+ removedAssociationModelIds: removeAssociationModelIds,
943
+ };
944
+ }
945
+ }
946
+
947
+ export default DatabaseService;