@friggframework/core 0.2.31-v1-alpha-package-update.0 → 1.0.1-v1-alpha-package-update.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.
Files changed (193) hide show
  1. package/assertions/CHANGELOG.md +87 -0
  2. package/assertions/LICENSE.md +9 -0
  3. package/assertions/README.md +3 -0
  4. package/assertions/bump.txt +1 -0
  5. package/assertions/get.js +139 -0
  6. package/assertions/index.js +19 -0
  7. package/assertions/is-equal.js +17 -0
  8. package/associations/LICENSE.md +9 -0
  9. package/associations/README.md +3 -0
  10. package/associations/association.js +78 -0
  11. package/associations/bump3.txt +0 -0
  12. package/associations/jest.config.js +5 -0
  13. package/associations/model.js +54 -0
  14. package/bump.txt +1 -0
  15. package/core/.eslintrc.json +3 -0
  16. package/{Delegate.js → core/Delegate.js} +1 -1
  17. package/core/LICENSE.md +9 -0
  18. package/{Worker.js → core/Worker.js} +2 -2
  19. package/core/bump3.txt +0 -0
  20. package/{create-handler.js → core/create-handler.js} +2 -2
  21. package/core/index.js +6 -0
  22. package/core/jest.config.js +5 -0
  23. package/database/.eslintrc.json +3 -0
  24. package/database/CHANGELOG.md +97 -0
  25. package/database/LICENSE.md +9 -0
  26. package/database/README.md +3 -0
  27. package/database/bump3.txt +0 -0
  28. package/database/index.js +17 -0
  29. package/database/jest.config.js +5 -0
  30. package/database/models/IndividualUser.js +76 -0
  31. package/database/models/OrganizationUser.js +29 -0
  32. package/database/models/State.js +9 -0
  33. package/database/models/Token.js +70 -0
  34. package/database/models/UserModel.js +7 -0
  35. package/database/mongo.js +45 -0
  36. package/database/mongoose.js +5 -0
  37. package/encrypt/.eslintrc.json +3 -0
  38. package/encrypt/CHANGELOG.md +65 -0
  39. package/encrypt/Cryptor.js +236 -0
  40. package/encrypt/Cryptor.test.js +32 -0
  41. package/encrypt/LICENSE.md +9 -0
  42. package/encrypt/README.md +3 -0
  43. package/encrypt/aes.js +27 -0
  44. package/encrypt/bump3.txt +0 -0
  45. package/encrypt/encrypt.js +124 -0
  46. package/encrypt/encrypt.test.js +1068 -0
  47. package/encrypt/index.js +3 -0
  48. package/encrypt/jest.config.js +5 -0
  49. package/encrypt/test-encrypt.js +107 -0
  50. package/errors/.eslintrc.json +3 -0
  51. package/errors/CHANGELOG.md +44 -0
  52. package/errors/LICENSE.md +9 -0
  53. package/errors/README.md +3 -0
  54. package/errors/base-error.js +23 -0
  55. package/errors/base-error.test.js +32 -0
  56. package/errors/bump.txt +1 -0
  57. package/errors/bump3.txt +0 -0
  58. package/errors/fetch-error.js +72 -0
  59. package/errors/fetch-error.test.js +79 -0
  60. package/errors/halt-error.js +10 -0
  61. package/errors/halt-error.test.js +11 -0
  62. package/errors/index.js +15 -0
  63. package/errors/jest.config.js +5 -0
  64. package/errors/validation-errors.js +23 -0
  65. package/errors/validation-errors.test.js +120 -0
  66. package/eslint-config/.eslintrc.json +3 -0
  67. package/eslint-config/CHANGELOG.md +17 -0
  68. package/eslint-config/LICENSE.md +9 -0
  69. package/eslint-config/README.md +3 -0
  70. package/eslint-config/bump3.txt +0 -0
  71. package/eslint-config/index.js +38 -0
  72. package/index.js +29 -5
  73. package/integrations/.eslintrc.json +3 -0
  74. package/integrations/CHANGELOG.md +244 -0
  75. package/integrations/LICENSE.md +9 -0
  76. package/integrations/README.md +3 -0
  77. package/integrations/bump3.txt +0 -0
  78. package/integrations/create-frigg-backend.js +31 -0
  79. package/integrations/index.js +19 -0
  80. package/integrations/integration-base.js +162 -0
  81. package/integrations/integration-factory.js +166 -0
  82. package/integrations/integration-mapping.js +43 -0
  83. package/integrations/integration-model.js +42 -0
  84. package/integrations/integration-router.js +367 -0
  85. package/integrations/integration-user.js +144 -0
  86. package/integrations/jest-setup.js +2 -0
  87. package/integrations/jest-teardown.js +2 -0
  88. package/integrations/jest.config.js +12 -0
  89. package/integrations/options.js +54 -0
  90. package/integrations/test/integration-base.test.js +143 -0
  91. package/lambda/README.md +3 -0
  92. package/lambda/TimeoutCatcher.js +43 -0
  93. package/lambda/TimeoutCatcher.test.js +68 -0
  94. package/lambda/bump3.txt +0 -0
  95. package/lambda/index.js +3 -0
  96. package/lambda/jest.config.js +3 -0
  97. package/logs/.eslintrc.json +3 -0
  98. package/logs/CHANGELOG.md +57 -0
  99. package/logs/LICENSE.md +9 -0
  100. package/logs/README.md +3 -0
  101. package/logs/bump3.txt +0 -0
  102. package/logs/index.js +7 -0
  103. package/logs/jest.config.js +5 -0
  104. package/logs/logger.js +69 -0
  105. package/logs/logger.test.js +76 -0
  106. package/migrations/README.md +3 -0
  107. package/migrations/bump3.txt +0 -0
  108. package/migrations/index.js +9 -0
  109. package/migrations/jest.config.js +3 -0
  110. package/migrations/manager.js +33 -0
  111. package/migrations/migrator.js +170 -0
  112. package/migrations/options.js +28 -0
  113. package/module-plugin/.eslintrc.json +3 -0
  114. package/module-plugin/CHANGELOG.md +224 -0
  115. package/module-plugin/LICENSE.md +9 -0
  116. package/module-plugin/ModuleConstants.js +10 -0
  117. package/module-plugin/README.md +3 -0
  118. package/module-plugin/auther.js +342 -0
  119. package/module-plugin/bump3.txt +0 -0
  120. package/module-plugin/credential.js +22 -0
  121. package/module-plugin/entity-manager.js +70 -0
  122. package/module-plugin/entity.js +46 -0
  123. package/module-plugin/index.js +25 -0
  124. package/module-plugin/jest-setup.js +3 -0
  125. package/module-plugin/jest-teardown.js +2 -0
  126. package/module-plugin/jest.config.js +20 -0
  127. package/module-plugin/manager.js +169 -0
  128. package/module-plugin/module-factory.js +60 -0
  129. package/module-plugin/requester/api-key.js +36 -0
  130. package/module-plugin/requester/basic.js +43 -0
  131. package/module-plugin/requester/oauth-2.js +219 -0
  132. package/module-plugin/requester/requester.js +150 -0
  133. package/module-plugin/requester/requester.test.js +28 -0
  134. package/module-plugin/test/auther.test.js +97 -0
  135. package/module-plugin/test/mock-api/api.js +29 -0
  136. package/module-plugin/test/mock-api/definition.js +48 -0
  137. package/module-plugin/test/mock-api/mocks/hubspot.js +43 -0
  138. package/package.json +37 -12
  139. package/prettier-config/.eslintrc.json +3 -0
  140. package/prettier-config/CHANGELOG.md +17 -0
  141. package/prettier-config/LICENSE.md +9 -0
  142. package/prettier-config/README.md +3 -0
  143. package/prettier-config/bump3.txt +0 -0
  144. package/prettier-config/index.js +6 -0
  145. package/syncs/README.md +3 -0
  146. package/syncs/bump3.txt +0 -0
  147. package/syncs/jest.config.js +5 -0
  148. package/syncs/manager.js +466 -0
  149. package/syncs/model.js +62 -0
  150. package/syncs/sync.js +114 -0
  151. package/test-environment/.eslintrc.json +3 -0
  152. package/test-environment/Authenticator.js +73 -0
  153. package/test-environment/CHANGELOG.md +46 -0
  154. package/test-environment/LICENSE.md +9 -0
  155. package/test-environment/README.md +3 -0
  156. package/test-environment/auther-definition-method-tester.js +45 -0
  157. package/test-environment/auther-definition-tester.js +104 -0
  158. package/test-environment/bump3.txt +0 -0
  159. package/test-environment/index.js +24 -0
  160. package/test-environment/integration-validator.js +2 -0
  161. package/test-environment/jest-global-setup.js +6 -0
  162. package/test-environment/jest-global-teardown.js +3 -0
  163. package/test-environment/jest-preset.js +14 -0
  164. package/test-environment/mock-api-readme.md +102 -0
  165. package/test-environment/mock-api.js +284 -0
  166. package/test-environment/mock-integration.js +82 -0
  167. package/test-environment/mongodb.js +22 -0
  168. package/test-environment/override-environment.js +11 -0
  169. package/types/CHANGELOG.md +49 -0
  170. package/types/README.md +24 -0
  171. package/types/assertions/index.d.ts +83 -0
  172. package/types/associations/index.d.ts +74 -0
  173. package/types/bump3.txt +0 -0
  174. package/types/core/index.d.ts +54 -0
  175. package/types/database/index.d.ts +3 -0
  176. package/types/encrypt/index.d.ts +5 -0
  177. package/types/errors/index.d.ts +66 -0
  178. package/types/eslint-config/index.d.ts +41 -0
  179. package/types/index.d.ts +14 -0
  180. package/types/integrations/index.d.ts +191 -0
  181. package/types/lambda/index.d.ts +31 -0
  182. package/types/logs/index.d.ts +5 -0
  183. package/types/module-plugin/index.d.ts +293 -0
  184. package/types/prettier-config/index.d.ts +6 -0
  185. package/types/syncs/index.d.ts +128 -0
  186. package/types/test-environment/index.d.ts +17 -0
  187. package/types/tsconfig.json +103 -0
  188. /package/{.eslintrc.json → assertions/.eslintrc.json} +0 -0
  189. /package/{bump3.txt → assertions/bump3.txt} +0 -0
  190. /package/{jest.config.js → assertions/jest.config.js} +0 -0
  191. /package/{CHANGELOG.md → core/CHANGELOG.md} +0 -0
  192. /package/{README.md → core/README.md} +0 -0
  193. /package/{load-installed-modules.js → core/load-installed-modules.js} +0 -0
@@ -0,0 +1,466 @@
1
+ const _ = require("lodash");
2
+ const moment = require("moment");
3
+ const mongoose = require("mongoose");
4
+ const SyncObject = require("./sync");
5
+ const { debug } = require("packages/logs");
6
+ const { get } = require("../assertions");
7
+ const { Sync } = require("./model");
8
+
9
+ class SyncManager {
10
+ constructor(params) {
11
+ super(params);
12
+ // TODO verify type????????
13
+ // this.primaryModule = getAndVerifyType(params, 'primary', ModuleManager);
14
+ // this.secondaryModule = getAndVerifyType(
15
+ // params,
16
+ // 'secondary',
17
+ // ModuleManager
18
+ // );
19
+ this.SyncObjectClass = getAndVerifyType(
20
+ params,
21
+ "syncObjectClass",
22
+ SyncObject
23
+ );
24
+ this.ignoreEmptyMatchValues = get(params, "ignoreEmptyMatchValues", true);
25
+ this.isUnidirectionalSync = get(params, "isUnidirectionalSync", false);
26
+ this.useFirstMatchingDuplicate = get(
27
+ params,
28
+ "useFirstMatchingDuplicate",
29
+ true
30
+ );
31
+ this.omitEmptyStringsFromData = get(
32
+ params,
33
+ "omitEmptyStringsFromData",
34
+ true
35
+ );
36
+
37
+ this.integration = get(params, "integration", null); // TODO Change to type validation
38
+
39
+ Sync = new Sync();
40
+ }
41
+
42
+ // calls getAllSyncObjects() on the modules and then finds the difference between each. The Primary Module
43
+ // takes precedence unless the field is an empty string or null
44
+ async initialSync() {
45
+ const time0 = parseInt(moment().format("x"));
46
+ const primaryEntityId = await this.primaryModule.entity.id;
47
+ const secondaryEntityId = await this.secondaryModule.entity.id;
48
+
49
+ // get array of sync objects
50
+ let primaryArr = await this.primaryModule.getAllSyncObjects(
51
+ this.SyncObjectClass
52
+ );
53
+ const primaryArrayInitialCount = primaryArr.length;
54
+ const time1 = parseInt(moment().format("x"));
55
+ debug(
56
+ `${primaryArr.length} number of ${
57
+ this.SyncObjectClass.name
58
+ } retrieved from ${this.primaryModule.constructor.getName()} in ${
59
+ time1 - time0
60
+ } ms`
61
+ );
62
+ let secondaryArr = await this.secondaryModule.getAllSyncObjects(
63
+ this.SyncObjectClass
64
+ );
65
+ const secondaryArrayInitialCount = secondaryArr.length;
66
+ const time2 = parseInt(moment().format("x"));
67
+ debug(
68
+ `${secondaryArr.length} number of ${
69
+ this.SyncObjectClass.name
70
+ } retrieved from ${this.secondaryModule.constructor.getName()} in ${
71
+ time2 - time1
72
+ } ms`
73
+ );
74
+
75
+ // ignore the empty match values
76
+ if (this.ignoreEmptyMatchValues) {
77
+ const primaryCountBefore = primaryArr.length;
78
+ primaryArr = primaryArr.filter((obj) => !obj.missingMatchData);
79
+ const primaryCountAfter = primaryArr.length;
80
+ const secondaryCountBefore = secondaryArr.length;
81
+ secondaryArr = secondaryArr.filter((obj) => !obj.missingMatchData);
82
+ const secondaryCountAfter = secondaryArr.length;
83
+ debug(
84
+ `Ignoring ${primaryCountBefore - primaryCountAfter} ${
85
+ this.SyncObjectClass.name
86
+ } objects from ${this.primaryModule.constructor.getName()}`
87
+ );
88
+ debug(
89
+ `Ignoring ${secondaryCountBefore - secondaryCountAfter} ${
90
+ this.SyncObjectClass.name
91
+ } objects from ${this.secondaryModule.constructor.getName()}`
92
+ );
93
+ }
94
+ if (this.useFirstMatchingDuplicate) {
95
+ primaryArr = _.uniqBy(primaryArr, "matchHash");
96
+ debug(
97
+ `${primaryArr.length} Objects remaining after removing duplicates from Primary Array`
98
+ );
99
+ secondaryArr = _.uniqBy(secondaryArr, "matchHash");
100
+ debug(
101
+ `${secondaryArr.length} Objects remaining after removing duplicates from Secondary Array`
102
+ );
103
+ }
104
+ const primaryUpdate = [];
105
+ const secondaryUpdate = [];
106
+ // PrimaryIntersection is an array where at least one matching object was found inside
107
+ // SecondaryArray that matched the inspected object from Primary.
108
+ // The only catch is, there will definitely be duplicates unless self filtered
109
+ const primaryIntersection = primaryArr.filter((e1) =>
110
+ secondaryArr.some((e2) => e1.equals(e2))
111
+ );
112
+ // SecondaryIntersection is an array where at least one matching object was found inside
113
+ // primaryIntersection that matched the inspected object from secondaryArray.
114
+ // The only catch is, there will definitely be duplicates unless self filtered
115
+ const secondaryIntersection = secondaryArr.filter((e1) =>
116
+ primaryIntersection.some((e2) => e1.equals(e2))
117
+ );
118
+ const secondaryCreate = primaryArr.filter(
119
+ (e1) => !secondaryArr.some((e2) => e1.equals(e2))
120
+ );
121
+ const primaryCreate = secondaryArr.filter(
122
+ (e1) => !primaryArr.some((e2) => e1.equals(e2))
123
+ );
124
+
125
+ // process the intersections and see which ones need to be updated.
126
+ for (const primaryObj of primaryIntersection) {
127
+ const secondaryObj = secondaryIntersection.find((e1) =>
128
+ e1.equals(primaryObj)
129
+ );
130
+
131
+ let primaryUpdated = false;
132
+ let secondaryUpdated = false;
133
+
134
+ for (const key in primaryObj.data) {
135
+ let valuesAreNotEquivalent = true; // Default to this just to be safe
136
+ // Make sure we're not comparing a number 0 to a empty string/null/undefined.
137
+ if (_.isEqual(primaryObj.data[key], secondaryObj.data[key])) {
138
+ // This should basically tell us if both values are falsy, in which case we're good
139
+ valuesAreNotEquivalent = false;
140
+ } else if (
141
+ typeof primaryObj.data[key] === "number" ||
142
+ typeof secondaryObj.data[key] === "number"
143
+ ) {
144
+ // This should try comparing if at least one of the two are numbers
145
+ valuesAreNotEquivalent =
146
+ primaryObj.data[key] !== secondaryObj.data[key];
147
+ } else if (!primaryObj.data[key] && !secondaryObj.data[key]) {
148
+ valuesAreNotEquivalent = false;
149
+ }
150
+
151
+ if (valuesAreNotEquivalent) {
152
+ if (
153
+ primaryObj.dataKeyIsReplaceable(key) &&
154
+ !secondaryObj.dataKeyIsReplaceable(key) &&
155
+ !this.isUnidirectionalSync
156
+ ) {
157
+ primaryObj.data[key] = secondaryObj.data[key];
158
+ primaryUpdated = true;
159
+ } else if (!primaryObj.dataKeyIsReplaceable(key)) {
160
+ secondaryObj.data[key] = primaryObj.data[key];
161
+ secondaryUpdated = true;
162
+ }
163
+ }
164
+ }
165
+ if (primaryUpdated && !this.isUnidirectionalSync) {
166
+ primaryUpdate.push(primaryObj);
167
+ }
168
+ if (secondaryUpdated) {
169
+ secondaryUpdate.push(secondaryObj);
170
+ }
171
+
172
+ const createdObj = await this.createSyncDBObject(
173
+ [primaryObj, secondaryObj],
174
+ [primaryEntityId, secondaryEntityId]
175
+ );
176
+
177
+ primaryObj.setSyncId(createdObj.id);
178
+ secondaryObj.setSyncId(createdObj.id);
179
+ }
180
+ debug(
181
+ `Found ${
182
+ primaryUpdate.length
183
+ } for updating in ${this.primaryModule.constructor.getName()}`
184
+ );
185
+ debug(
186
+ `Found ${
187
+ primaryCreate.length
188
+ } for creating in ${this.primaryModule.constructor.getName()}`
189
+ );
190
+ debug(
191
+ `Found ${
192
+ secondaryUpdate.length
193
+ } for updating in ${this.secondaryModule.constructor.getName()}`
194
+ );
195
+ debug(
196
+ `Found ${
197
+ secondaryCreate.length
198
+ } for creating in ${this.secondaryModule.constructor.getName()}`
199
+ );
200
+
201
+ const time3 = parseInt(moment().format("x"));
202
+ debug(`Sorting complete in ${time3 - time2} ms`);
203
+
204
+ // create the database entries for the
205
+ if (!this.isUnidirectionalSync) {
206
+ for (const secondaryObj of primaryCreate) {
207
+ const createdObj = await this.createSyncDBObject(
208
+ [secondaryObj],
209
+ [secondaryEntityId, primaryEntityId]
210
+ );
211
+
212
+ secondaryObj.setSyncId(createdObj.id);
213
+ }
214
+ }
215
+
216
+ for (const primaryObj of secondaryCreate) {
217
+ const createdObj = await this.createSyncDBObject(
218
+ [primaryObj],
219
+ [primaryEntityId, secondaryEntityId]
220
+ );
221
+ primaryObj.setSyncId(createdObj.id);
222
+ }
223
+ const time4 = parseInt(moment().format("x"));
224
+ debug(`Sync objects create in DB in ${time4 - time3} ms`);
225
+
226
+ // call the batch update/creates
227
+ let time5 = parseInt(moment().format("x"));
228
+ let time6 = parseInt(moment().format("x"));
229
+ if (!this.isUnidirectionalSync) {
230
+ await this.primaryModule.batchUpdateSyncObjects(primaryUpdate, this);
231
+ time5 = parseInt(moment().format("x"));
232
+ debug(
233
+ `Updated ${primaryUpdate.length} ${
234
+ this.SyncObjectClass.name
235
+ }s in ${this.primaryModule.constructor.getName()} in ${
236
+ time5 - time4
237
+ } ms`
238
+ );
239
+ await this.primaryModule.batchCreateSyncObjects(primaryCreate, this);
240
+ time6 = parseInt(moment().format("x"));
241
+ debug(
242
+ `Created ${primaryCreate.length} ${
243
+ this.SyncObjectClass.name
244
+ }s in ${this.primaryModule.constructor.getName()} in ${
245
+ time6 - time5
246
+ } ms`
247
+ );
248
+ }
249
+
250
+ await this.secondaryModule.batchUpdateSyncObjects(secondaryUpdate, this);
251
+ const time7 = parseInt(moment().format("x"));
252
+ debug(
253
+ `Updated ${secondaryUpdate.length} ${
254
+ this.SyncObjectClass.name
255
+ }s in ${this.secondaryModule.constructor.getName()} in ${
256
+ time7 - time6
257
+ } ms`
258
+ );
259
+
260
+ await this.secondaryModule.batchCreateSyncObjects(secondaryCreate, this);
261
+ const time8 = parseInt(moment().format("x"));
262
+ debug(
263
+ `${primaryArrayInitialCount} number of ${
264
+ this.SyncObjectClass.name
265
+ } objects retrieved from ${this.primaryModule.constructor.getName()} in ${
266
+ time1 - time0
267
+ } ms`
268
+ );
269
+ debug(
270
+ `${secondaryArrayInitialCount} number of ${
271
+ this.SyncObjectClass.name
272
+ } objects retrieved from ${this.secondaryModule.constructor.getName()} in ${
273
+ time2 - time1
274
+ } ms`
275
+ );
276
+ debug(`Sorting complete in ${time3 - time2} ms`);
277
+ debug(`Sync objects create in DB in ${time4 - time3} ms`);
278
+ debug(
279
+ `Updated ${primaryUpdate.length} ${
280
+ this.SyncObjectClass.name
281
+ }s in ${this.primaryModule.constructor.getName()} in ${time5 - time4} ms`
282
+ );
283
+ debug(
284
+ `Created ${primaryCreate.length} ${
285
+ this.SyncObjectClass.name
286
+ }s in ${this.primaryModule.constructor.getName()} in ${time6 - time5} ms`
287
+ );
288
+ debug(
289
+ `Updated ${secondaryUpdate.length} ${
290
+ this.SyncObjectClass.name
291
+ }s in ${this.secondaryModule.constructor.getName()} in ${
292
+ time7 - time6
293
+ } ms`
294
+ );
295
+ debug(
296
+ `Created ${secondaryCreate.length} ${
297
+ this.SyncObjectClass.name
298
+ }s in ${this.secondaryModule.constructor.getName()} in ${
299
+ time8 - time7
300
+ } ms`
301
+ );
302
+ }
303
+
304
+ async createSyncDBObject(objArr, entities) {
305
+ const entityIds = entities.map(
306
+ (ent) => ({ $elemMatch: { $eq: mongoose.Types.ObjectId(ent) } })
307
+ // return {"$elemMatch": {"$eq": ent}};
308
+ );
309
+ const dataIdentifiers = [];
310
+ for (const index in objArr) {
311
+ dataIdentifiers.push({
312
+ entity: entities[index],
313
+ id: objArr[index].dataIdentifier,
314
+ hash: objArr[index].dataIdentifierHash,
315
+ });
316
+ }
317
+ const primaryObj = objArr[0];
318
+
319
+ const createSyncObj = {
320
+ name: primaryObj.getName(),
321
+ entities,
322
+ hash: primaryObj.getHashData({
323
+ omitEmptyStringsFromData: this.omitEmptyStringsFromData,
324
+ }),
325
+ dataIdentifiers,
326
+ };
327
+ const filter = {
328
+ name: primaryObj.getName(),
329
+ dataIdentifiers: {
330
+ $elemMatch: {
331
+ id: primaryObj.dataIdentifier,
332
+ entity: entities[0],
333
+ },
334
+ },
335
+ entities: { $all: entityIds },
336
+ // entities
337
+ };
338
+
339
+ return await Sync.upsert(filter, createSyncObj);
340
+ }
341
+
342
+ // Automatically syncs the objects with the secondary module if the object was updated
343
+ async sync(syncObjects) {
344
+ const batchUpdates = [];
345
+ const batchCreates = [];
346
+ const noChange = [];
347
+ const primaryEntityId = await this.primaryModule.entity.id;
348
+ const secondaryEntityId = await this.secondaryModule.entity.id;
349
+
350
+ const secondaryModuleName = this.secondaryModule.constructor.getName();
351
+ for (const primaryObj of syncObjects) {
352
+ const dataHash = primaryObj.getHashData({
353
+ omitEmptyStringsFromData: this.omitEmptyStringsFromData,
354
+ });
355
+
356
+ // get the sync object in the database if it exists
357
+ let syncObj = await Sync.getSyncObject(
358
+ primaryObj.getName(),
359
+ primaryObj.dataIdentifier,
360
+ primaryEntityId
361
+ );
362
+
363
+ if (syncObj) {
364
+ debug("Sync object found, evaluating...");
365
+ const hashMatch = syncObj.hash === dataHash;
366
+ const dataIdentifierLength = syncObj.dataIdentifiers.length;
367
+
368
+ if (!hashMatch && dataIdentifierLength > 1) {
369
+ debug(
370
+ "Previously successful sync, but hashes don't match. Updating."
371
+ );
372
+ const secondaryObj = new this.SyncObjectClass({
373
+ data: primaryObj.data,
374
+ dataIdentifier: Sync.getEntityObjIdForEntityIdFromObject(
375
+ syncObj,
376
+ secondaryEntityId
377
+ ),
378
+ moduleName: secondaryModuleName,
379
+ useMapping: false,
380
+ });
381
+ secondaryObj.setSyncId(syncObj.id);
382
+ batchUpdates.push(secondaryObj);
383
+ } else if (hashMatch && dataIdentifierLength > 1) {
384
+ debug(
385
+ "Data hashes match, no updates or creates needed for this one."
386
+ );
387
+ noChange.push(syncObj);
388
+ }
389
+
390
+ if (dataIdentifierLength === 1) {
391
+ debug(
392
+ "We have only one data Identifier, which means we don't have a record in the secondary app for whatever reason (failure or filter). So, creating."
393
+ );
394
+ primaryObj.setSyncId(syncObj.id);
395
+ batchCreates.push(primaryObj);
396
+ }
397
+ } else {
398
+ debug(
399
+ "No sync object, so we'll try creating, first creating an object"
400
+ );
401
+ syncObj = await this.createSyncDBObject(
402
+ [primaryObj],
403
+ [primaryEntityId, secondaryEntityId]
404
+ );
405
+ primaryObj.setSyncId(syncObj.id);
406
+ batchCreates.push(primaryObj);
407
+ }
408
+ }
409
+ const updateRes =
410
+ batchUpdates.length > 0
411
+ ? await this.secondaryModule.batchUpdateSyncObjects(batchUpdates, this)
412
+ : [];
413
+ const createRes =
414
+ batchCreates.length > 0
415
+ ? await this.secondaryModule.batchCreateSyncObjects(batchCreates, this)
416
+ : [];
417
+ return updateRes.concat(createRes).concat(noChange);
418
+ }
419
+
420
+ // takes in:
421
+ // 1. the Sync Id of an object in our database
422
+ // 2. the object Id in the form of a json object for example:
423
+ // {
424
+ // companyId: 12,
425
+ // saleId:524
426
+ // }
427
+ // 3. the module manager calling the function
428
+ async confirmCreate(syncObj, createdId, moduleManager) {
429
+ const dataIdentifier = {
430
+ entity: await moduleManager.entity.id,
431
+ id: createdId,
432
+ hash: this.SyncObjectClass.hashJSON(createdId),
433
+ };
434
+ // No matter what, save the hash because why not?
435
+ // TODO this is suboptimal because it does 2 DB requests where only 1 is needed
436
+ // TODO If you want to get even more optimized, batch any/all updates together.
437
+ // Also this is only needed because of the case where an "update" becomes a "create" when we find only
438
+ // 1 data identifier. So, during `sync()`, if we see that the hashes don't match, we check for DataIDs and
439
+ // decide to create in the "target" or "secondary" because we know it failed for some reason. We also want
440
+ // to hold off on updating the hash in case the create fails for some reason again.
441
+
442
+ await Sync.update(syncObj.syncId, {
443
+ hash: syncObj.getHashData({
444
+ omitEmptyStringsFromData: this.omitEmptyStringsFromData,
445
+ }),
446
+ });
447
+
448
+ const result = await Sync.addDataIdentifier(syncObj.syncId, dataIdentifier);
449
+
450
+ return result;
451
+ }
452
+
453
+ async confirmUpdate(syncObj) {
454
+ debug("Successfully updated secondaryObject. Updating the hash in the DB");
455
+ const result = await Sync.update(syncObj.syncId, {
456
+ hash: syncObj.getHashData({
457
+ omitEmptyStringsFromData: this.omitEmptyStringsFromData,
458
+ }),
459
+ });
460
+ debug("Success");
461
+
462
+ return result;
463
+ }
464
+ }
465
+
466
+ module.exports = SyncManager;
package/syncs/model.js ADDED
@@ -0,0 +1,62 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const schema = new mongoose.Schema({
4
+ entities: [
5
+ { type: mongoose.Schema.Types.ObjectId, ref: "Entity", required: true },
6
+ ],
7
+ hash: { type: String, required: true },
8
+ name: { type: String, required: true },
9
+ dataIdentifiers: [
10
+ {
11
+ entity: {
12
+ type: mongoose.Schema.Types.ObjectId,
13
+ ref: "Entity",
14
+ required: true,
15
+ },
16
+ id: { type: Object, required: true },
17
+ hash: { type: String, required: true },
18
+ },
19
+ ],
20
+ });
21
+
22
+ schema.statics({
23
+ getSyncObject: async function (name, dataIdentifier, entity) {
24
+ // const syncList = await this.list({name:name,entities: {"$in": entities}, "entityIds.idHash":entityIdHash });
25
+ const syncList = await this.find({
26
+ name: name,
27
+ dataIdentifiers: { $elemMatch: { id: dataIdentifier, entity } },
28
+ });
29
+
30
+ if (syncList.length === 1) {
31
+ return syncList[0];
32
+ } else if (syncList.length === 0) {
33
+ return null;
34
+ } else {
35
+ throw new Error(
36
+ `There are multiple sync objects with the name ${name}, for entities [${syncList[0].entities}] [${syncList[1].entities}]`
37
+ );
38
+ }
39
+ },
40
+
41
+ addDataIdentifier: async function (id, dataIdentifier) {
42
+ return await this.update(
43
+ { _id: id },
44
+ {},
45
+ { dataIdentifiers: dataIdentifier }
46
+ );
47
+ },
48
+
49
+ getEntityObjIdForEntityIdFromObject: function (syncObj, entityId) {
50
+ for (let dataIdentifier of syncObj.dataIdentifiers) {
51
+ if (dataIdentifier.entity.toString() === entityId) {
52
+ return dataIdentifier.id;
53
+ }
54
+ }
55
+ throw new Error(
56
+ `Sync object does not have DataIdentifier for entityId: ${entityId}`
57
+ );
58
+ },
59
+ });
60
+
61
+ const Sync = mongoose.models.Sync || mongoose.model("Sync", schema);
62
+ module.exports = { Sync };
package/syncs/sync.js ADDED
@@ -0,0 +1,114 @@
1
+ const md5 = require("md5");
2
+ const ModuleManager = require('../module-plugin');
3
+ const { debug } = require("packages/logs");
4
+ const { get } = require("packages/assertions");
5
+
6
+ class Sync {
7
+ static Config = {
8
+ name: "Sync",
9
+
10
+ // an array of keys we will use to form an object and then hash it. Order matters here
11
+ // because it will effect how the hash results
12
+ keys: [],
13
+
14
+ // matchOn is an array of keys that make the variable unique when combined together
15
+ // and is used to sync with the other objects
16
+ // matchOn keys _have_ to have a value, otherwise the object is not considered a match
17
+ matchOn: [],
18
+
19
+ // a key value mapping of module to then a list of keys that will map to
20
+ // an a function that takes in the module object and return the value from it
21
+ // format as follows:
22
+ // {
23
+ // ModuleName:{
24
+ // firstName:(moduleObject)=>{moduleObject['name'][0]},
25
+ // lastName:(moduleObject)=>{moduleObject['name'][1]},
26
+ // },
27
+ // ....
28
+ // }
29
+ moduleMap: {},
30
+ reverseModuleMap: {},
31
+ };
32
+ constructor(params) {
33
+ this.data = {};
34
+
35
+ let data = get(params, "data");
36
+ this.moduleName = get(params, "moduleName");
37
+ this.dataIdentifier = get(params, "dataIdentifier");
38
+ this.useMapping = get(params, "useMapping", true); // Use with caution...
39
+
40
+ this.dataIdentifierHash = this.constructor.hashJSON(this.dataIdentifier);
41
+
42
+ if (this.useMapping) {
43
+ for (let key of this.constructor.Config.keys) {
44
+ this.data[key] =
45
+ this.constructor.Config.moduleMap[this.moduleName][key](data);
46
+ }
47
+ } else {
48
+ this.data = data;
49
+ }
50
+
51
+ // matchHash is used to find matches between two sync objects
52
+ // Match data _has_ to have a value
53
+ const matchHashData = [];
54
+ this.missingMatchData = false;
55
+ for (const key of this.constructor.Config.matchOn) {
56
+ if (!this.data[key]) {
57
+ this.missingMatchData = true;
58
+ debug(`Data key of ${key} was missing from MatchOn`);
59
+ }
60
+
61
+ matchHashData.push(this.data[key]);
62
+ }
63
+ this.matchHash = this.constructor.hashJSON(matchHashData);
64
+
65
+ this.syncId = null;
66
+ }
67
+
68
+ equals(syncObj) {
69
+ return this.matchHash === syncObj.matchHash;
70
+ }
71
+ dataKeyIsReplaceable(key) {
72
+ return this.data[key] === null || this.data[key] === "";
73
+ }
74
+
75
+ isModuleInMap(moduleName) {
76
+ return this.constructor.Config.moduleMap[name];
77
+ }
78
+
79
+ getName() {
80
+ return this.constructor.Config.name;
81
+ }
82
+
83
+ getHashData(params) {
84
+ let omitEmptyStringsFromData = get(
85
+ params,
86
+ "omitEmptyStringsFromData",
87
+ false
88
+ );
89
+ let orderedData = [];
90
+ for (let key of this.constructor.Config.keys) {
91
+ if (omitEmptyStringsFromData && this.data[key] === "") {
92
+ this.data[key] = undefined;
93
+ }
94
+ orderedData.push(this.data[key]);
95
+ }
96
+
97
+ return this.constructor.hashJSON(orderedData);
98
+ }
99
+
100
+ setSyncId(syncId) {
101
+ this.syncId = syncId;
102
+ }
103
+
104
+ reverseModuleMap(moduleName) {
105
+ return this.constructor.Config.reverseModuleMap[moduleName](this.data);
106
+ }
107
+
108
+ static hashJSON(data) {
109
+ let dataString = JSON.stringify(data, null, 2);
110
+ return md5(dataString);
111
+ }
112
+ }
113
+
114
+ module.exports = Sync;
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@friggframework/eslint-config"
3
+ }