@friggframework/core 2.0.0-next.8 → 2.0.0-next.81

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 (305) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +79 -8
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +268 -0
  30. package/database/encryption/field-encryption-service.js +226 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +222 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  69. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  72. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  73. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  74. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  75. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  76. package/generated/prisma-mongodb/runtime/library.js +146 -0
  77. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  78. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  79. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  80. package/generated/prisma-mongodb/schema.prisma +368 -0
  81. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  82. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  84. package/generated/prisma-mongodb/wasm.js +342 -0
  85. package/generated/prisma-postgresql/client.d.ts +1 -0
  86. package/generated/prisma-postgresql/client.js +4 -0
  87. package/generated/prisma-postgresql/default.d.ts +1 -0
  88. package/generated/prisma-postgresql/default.js +4 -0
  89. package/generated/prisma-postgresql/edge.d.ts +1 -0
  90. package/generated/prisma-postgresql/edge.js +357 -0
  91. package/generated/prisma-postgresql/index-browser.js +339 -0
  92. package/generated/prisma-postgresql/index.d.ts +25135 -0
  93. package/generated/prisma-postgresql/index.js +382 -0
  94. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  95. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  96. package/generated/prisma-postgresql/package.json +183 -0
  97. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  98. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  99. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  100. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  101. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  102. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  103. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  104. package/generated/prisma-postgresql/runtime/library.js +146 -0
  105. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  106. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  107. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  108. package/generated/prisma-postgresql/schema.prisma +351 -0
  109. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  110. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  111. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  112. package/generated/prisma-postgresql/wasm.js +364 -0
  113. package/handlers/WEBHOOKS.md +653 -0
  114. package/handlers/app-definition-loader.js +38 -0
  115. package/handlers/app-handler-helpers.js +57 -0
  116. package/handlers/backend-utils.js +262 -0
  117. package/handlers/database-migration-handler.js +227 -0
  118. package/handlers/integration-event-dispatcher.js +54 -0
  119. package/handlers/routers/HEALTHCHECK.md +342 -0
  120. package/handlers/routers/auth.js +15 -0
  121. package/handlers/routers/db-migration.handler.js +29 -0
  122. package/handlers/routers/db-migration.js +326 -0
  123. package/handlers/routers/health.js +516 -0
  124. package/handlers/routers/integration-defined-routers.js +45 -0
  125. package/handlers/routers/integration-webhook-routers.js +67 -0
  126. package/handlers/routers/user.js +63 -0
  127. package/handlers/routers/websocket.js +57 -0
  128. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  129. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  130. package/handlers/workers/db-migration.js +352 -0
  131. package/handlers/workers/dlq-processor.js +63 -0
  132. package/handlers/workers/integration-defined-workers.js +23 -0
  133. package/index.js +82 -46
  134. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  135. package/infrastructure/scheduler/index.js +33 -0
  136. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  137. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  138. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  139. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  140. package/integrations/index.js +12 -10
  141. package/integrations/integration-base.js +364 -55
  142. package/integrations/integration-router.js +376 -179
  143. package/integrations/options.js +1 -1
  144. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  145. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  146. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  147. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  148. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  149. package/integrations/repositories/integration-mapping-repository.js +156 -0
  150. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  151. package/integrations/repositories/integration-repository-factory.js +51 -0
  152. package/integrations/repositories/integration-repository-interface.js +144 -0
  153. package/integrations/repositories/integration-repository-mongo.js +330 -0
  154. package/integrations/repositories/integration-repository-postgres.js +385 -0
  155. package/integrations/repositories/process-repository-documentdb.js +311 -0
  156. package/integrations/repositories/process-repository-factory.js +53 -0
  157. package/integrations/repositories/process-repository-interface.js +136 -0
  158. package/integrations/repositories/process-repository-mongo.js +262 -0
  159. package/integrations/repositories/process-repository-postgres.js +380 -0
  160. package/integrations/repositories/process-update-ops-shared.js +112 -0
  161. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  162. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  163. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  164. package/integrations/use-cases/create-integration.js +83 -0
  165. package/integrations/use-cases/create-process.js +128 -0
  166. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  167. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  168. package/integrations/use-cases/get-integration-for-user.js +78 -0
  169. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  170. package/integrations/use-cases/get-integration-instance.js +83 -0
  171. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  172. package/integrations/use-cases/get-possible-integrations.js +27 -0
  173. package/integrations/use-cases/get-process.js +87 -0
  174. package/integrations/use-cases/index.js +19 -0
  175. package/integrations/use-cases/load-integration-context.js +71 -0
  176. package/integrations/use-cases/update-integration-messages.js +44 -0
  177. package/integrations/use-cases/update-integration-status.js +32 -0
  178. package/integrations/use-cases/update-integration.js +92 -0
  179. package/integrations/use-cases/update-process-metrics.js +205 -0
  180. package/integrations/use-cases/update-process-state.js +158 -0
  181. package/integrations/utils/map-integration-dto.js +37 -0
  182. package/jest-global-setup-noop.js +3 -0
  183. package/jest-global-teardown-noop.js +3 -0
  184. package/logs/logger.js +0 -4
  185. package/{module-plugin → modules}/index.js +0 -10
  186. package/modules/module-factory.js +56 -0
  187. package/modules/module.js +258 -0
  188. package/modules/repositories/module-repository-documentdb.js +335 -0
  189. package/modules/repositories/module-repository-factory.js +40 -0
  190. package/modules/repositories/module-repository-interface.js +129 -0
  191. package/modules/repositories/module-repository-mongo.js +408 -0
  192. package/modules/repositories/module-repository-postgres.js +453 -0
  193. package/modules/repositories/module-repository.js +345 -0
  194. package/modules/requester/api-key.js +52 -0
  195. package/modules/requester/oauth-2.js +396 -0
  196. package/modules/requester/requester.js +275 -0
  197. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  198. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  199. package/modules/tests/doubles/test-module-factory.js +16 -0
  200. package/modules/tests/doubles/test-module-repository.js +39 -0
  201. package/modules/use-cases/get-entities-for-user.js +32 -0
  202. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  203. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  204. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  205. package/modules/use-cases/get-module.js +74 -0
  206. package/modules/use-cases/process-authorization-callback.js +177 -0
  207. package/modules/use-cases/refresh-entity-options.js +72 -0
  208. package/modules/use-cases/test-module-auth.js +72 -0
  209. package/modules/utils/map-module-dto.js +18 -0
  210. package/package.json +82 -50
  211. package/prisma-mongodb/schema.prisma +368 -0
  212. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  213. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  214. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  215. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  216. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  217. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  218. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  219. package/prisma-postgresql/schema.prisma +351 -0
  220. package/queues/queuer-util.js +103 -21
  221. package/syncs/manager.js +468 -443
  222. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  223. package/syncs/repositories/sync-repository-factory.js +43 -0
  224. package/syncs/repositories/sync-repository-interface.js +109 -0
  225. package/syncs/repositories/sync-repository-mongo.js +239 -0
  226. package/syncs/repositories/sync-repository-postgres.js +319 -0
  227. package/syncs/sync.js +0 -1
  228. package/token/repositories/token-repository-documentdb.js +137 -0
  229. package/token/repositories/token-repository-factory.js +40 -0
  230. package/token/repositories/token-repository-interface.js +131 -0
  231. package/token/repositories/token-repository-mongo.js +219 -0
  232. package/token/repositories/token-repository-postgres.js +264 -0
  233. package/token/repositories/token-repository.js +219 -0
  234. package/types/associations/index.d.ts +0 -17
  235. package/types/core/index.d.ts +12 -4
  236. package/types/database/index.d.ts +10 -2
  237. package/types/encrypt/index.d.ts +5 -3
  238. package/types/integrations/index.d.ts +3 -8
  239. package/types/module-plugin/index.d.ts +17 -69
  240. package/types/syncs/index.d.ts +0 -17
  241. package/user/repositories/user-repository-documentdb.js +441 -0
  242. package/user/repositories/user-repository-factory.js +52 -0
  243. package/user/repositories/user-repository-interface.js +201 -0
  244. package/user/repositories/user-repository-mongo.js +308 -0
  245. package/user/repositories/user-repository-postgres.js +360 -0
  246. package/user/tests/doubles/test-user-repository.js +72 -0
  247. package/user/use-cases/authenticate-user.js +127 -0
  248. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  249. package/user/use-cases/create-individual-user.js +61 -0
  250. package/user/use-cases/create-organization-user.js +47 -0
  251. package/user/use-cases/create-token-for-user-id.js +30 -0
  252. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  253. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  254. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  255. package/user/use-cases/login-user.js +122 -0
  256. package/user/user.js +125 -0
  257. package/utils/backend-path.js +38 -0
  258. package/utils/index.js +6 -0
  259. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  260. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  261. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  262. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  263. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  264. package/websocket/repositories/websocket-connection-repository.js +161 -0
  265. package/assertions/is-equal.js +0 -17
  266. package/associations/model.js +0 -54
  267. package/database/models/IndividualUser.js +0 -76
  268. package/database/models/OrganizationUser.js +0 -29
  269. package/database/models/State.js +0 -9
  270. package/database/models/Token.js +0 -70
  271. package/database/models/UserModel.js +0 -7
  272. package/database/models/WebsocketConnection.js +0 -49
  273. package/database/mongo.js +0 -45
  274. package/database/mongoose.js +0 -5
  275. package/encrypt/Cryptor.test.js +0 -32
  276. package/encrypt/encrypt.js +0 -132
  277. package/encrypt/encrypt.test.js +0 -1069
  278. package/encrypt/test-encrypt.js +0 -107
  279. package/errors/base-error.test.js +0 -32
  280. package/errors/fetch-error.test.js +0 -79
  281. package/errors/halt-error.test.js +0 -11
  282. package/errors/validation-errors.test.js +0 -120
  283. package/integrations/create-frigg-backend.js +0 -31
  284. package/integrations/integration-factory.js +0 -251
  285. package/integrations/integration-mapping.js +0 -43
  286. package/integrations/integration-model.js +0 -46
  287. package/integrations/integration-user.js +0 -144
  288. package/integrations/test/integration-base.test.js +0 -144
  289. package/lambda/TimeoutCatcher.test.js +0 -68
  290. package/logs/logger.test.js +0 -76
  291. package/module-plugin/auther.js +0 -393
  292. package/module-plugin/credential.js +0 -22
  293. package/module-plugin/entity-manager.js +0 -70
  294. package/module-plugin/entity.js +0 -46
  295. package/module-plugin/manager.js +0 -169
  296. package/module-plugin/module-factory.js +0 -61
  297. package/module-plugin/requester/api-key.js +0 -36
  298. package/module-plugin/requester/oauth-2.js +0 -219
  299. package/module-plugin/requester/requester.js +0 -165
  300. package/module-plugin/requester/requester.test.js +0 -28
  301. package/module-plugin/test/auther.test.js +0 -97
  302. package/syncs/model.js +0 -62
  303. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  304. /package/{module-plugin → modules}/requester/basic.js +0 -0
  305. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * UpdateProcessMetrics Use Case
3
+ *
4
+ * Updates process metrics atomically via
5
+ * `processRepository.applyProcessUpdate`. This is the race-safe
6
+ * replacement for the original read-modify-write implementation — the
7
+ * long-standing TODO about lost updates under concurrent writers is
8
+ * now resolved.
9
+ *
10
+ * Split into two phases:
11
+ *
12
+ * 1. Atomic phase — counters and bounded error history. Uses
13
+ * $inc / $push+$slice (Mongo/DocumentDB) or jsonb_set with
14
+ * arithmetic expressions (Postgres) in a single UPDATE ... RETURNING
15
+ * so concurrent callers serialize at the DB layer.
16
+ *
17
+ * 2. Derived-fields phase — duration, recordsPerSecond,
18
+ * estimatedCompletion. Computed from the post-atomic snapshot and
19
+ * written via the legacy (non-atomic) `update()` method.
20
+ * Intentionally best-effort: under concurrent writers they reflect
21
+ * "whichever handler wrote last" — the same semantics they had
22
+ * before and all they've ever guaranteed. Preserved for backward
23
+ * compatibility with consumers (UI, WebSocket listeners).
24
+ *
25
+ * Optionally broadcasts progress via WebSocket service if provided.
26
+ *
27
+ * @example
28
+ * const updateMetrics = new UpdateProcessMetrics({ processRepository, websocketService });
29
+ * await updateMetrics.execute(processId, {
30
+ * processed: 100,
31
+ * success: 95,
32
+ * errors: 5,
33
+ * errorDetails: [{ contactId: 'abc', error: 'Missing email', timestamp: '...' }]
34
+ * });
35
+ */
36
+ class UpdateProcessMetrics {
37
+ /**
38
+ * @param {Object} params
39
+ * @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
40
+ * @param {Object} [params.websocketService] - Optional WebSocket service for progress broadcasting
41
+ */
42
+ constructor({ processRepository, websocketService }) {
43
+ if (!processRepository) {
44
+ throw new Error('processRepository is required');
45
+ }
46
+ this.processRepository = processRepository;
47
+ this.websocketService = websocketService;
48
+ }
49
+
50
+ /**
51
+ * Execute the use case to update process metrics
52
+ * @param {string} processId - Process ID to update
53
+ * @param {Object} metricsUpdate - Metrics to add/update
54
+ * @param {number} [metricsUpdate.processed=0] - Records processed in this batch
55
+ * @param {number} [metricsUpdate.success=0] - Successful records
56
+ * @param {number} [metricsUpdate.errors=0] - Failed records
57
+ * @param {Array} [metricsUpdate.errorDetails=[]] - Error details array
58
+ * @returns {Promise<Object>} Updated process record
59
+ * @throws {Error} If process not found or update fails
60
+ */
61
+ async execute(processId, metricsUpdate) {
62
+ if (!processId || typeof processId !== 'string') {
63
+ throw new Error('processId must be a non-empty string');
64
+ }
65
+ if (!metricsUpdate || typeof metricsUpdate !== 'object') {
66
+ throw new Error('metricsUpdate must be an object');
67
+ }
68
+
69
+ // Phase 1: atomic increments + bounded error history.
70
+ const increment = {};
71
+ const processed = metricsUpdate.processed || 0;
72
+ const success = metricsUpdate.success || 0;
73
+ const errors = metricsUpdate.errors || 0;
74
+ if (processed) increment['context.processedRecords'] = processed;
75
+ if (success) increment['results.aggregateData.totalSynced'] = success;
76
+ if (errors) increment['results.aggregateData.totalFailed'] = errors;
77
+
78
+ const pushSlice = {};
79
+ if (
80
+ Array.isArray(metricsUpdate.errorDetails) &&
81
+ metricsUpdate.errorDetails.length > 0
82
+ ) {
83
+ pushSlice['results.aggregateData.errors'] = {
84
+ values: metricsUpdate.errorDetails,
85
+ keepLast: 100,
86
+ };
87
+ }
88
+
89
+ const hasAtomicWork =
90
+ Object.keys(increment).length > 0 ||
91
+ Object.keys(pushSlice).length > 0;
92
+
93
+ let updatedProcess;
94
+ try {
95
+ if (hasAtomicWork) {
96
+ updatedProcess = await this.processRepository.applyProcessUpdate(
97
+ processId,
98
+ { increment, pushSlice }
99
+ );
100
+ } else {
101
+ // All-zero update (e.g., empty batch) — nothing to persist;
102
+ // just read current state for the derived-fields pass.
103
+ updatedProcess = await this.processRepository.findById(
104
+ processId
105
+ );
106
+ }
107
+ } catch (error) {
108
+ throw new Error(
109
+ `Failed to update process metrics: ${error.message}`
110
+ );
111
+ }
112
+
113
+ if (!updatedProcess) {
114
+ throw new Error(`Process not found: ${processId}`);
115
+ }
116
+
117
+ // Phase 2: derived metrics (non-atomic, best-effort). Preserved
118
+ // for backward compatibility — these were always stale under
119
+ // concurrent writers even before this refactor.
120
+ const context = updatedProcess.context || {};
121
+ const results = updatedProcess.results || { aggregateData: {} };
122
+ if (!results.aggregateData) results.aggregateData = {};
123
+
124
+ if (context.processedRecords > 0 || context.totalRecords > 0) {
125
+ const startTime = new Date(
126
+ context.startTime || updatedProcess.createdAt
127
+ );
128
+ const elapsed = Date.now() - startTime.getTime();
129
+ results.aggregateData.duration = elapsed;
130
+
131
+ if (elapsed > 0 && context.processedRecords > 0) {
132
+ results.aggregateData.recordsPerSecond =
133
+ context.processedRecords / (elapsed / 1000);
134
+ } else {
135
+ results.aggregateData.recordsPerSecond = 0;
136
+ }
137
+
138
+ if (context.totalRecords > 0 && context.processedRecords > 0) {
139
+ const remaining =
140
+ context.totalRecords - context.processedRecords;
141
+ if (results.aggregateData.recordsPerSecond > 0) {
142
+ const etaMs =
143
+ (remaining / results.aggregateData.recordsPerSecond) *
144
+ 1000;
145
+ const eta = new Date(Date.now() + etaMs);
146
+ context.estimatedCompletion = eta.toISOString();
147
+ }
148
+ }
149
+
150
+ try {
151
+ updatedProcess = await this.processRepository.update(
152
+ processId,
153
+ { context, results }
154
+ );
155
+ } catch (error) {
156
+ // Derived-field write failures are NON-FATAL — atomic
157
+ // counters from phase 1 already landed. Log and return the
158
+ // post-atomic snapshot.
159
+ console.error(
160
+ '[UpdateProcessMetrics] derived-fields write failed (non-fatal):',
161
+ error.message
162
+ );
163
+ }
164
+ }
165
+
166
+ if (this.websocketService) {
167
+ await this._broadcastProgress(updatedProcess);
168
+ }
169
+
170
+ return updatedProcess;
171
+ }
172
+
173
+ /**
174
+ * Broadcast progress update via WebSocket
175
+ * @private
176
+ */
177
+ async _broadcastProgress(process) {
178
+ try {
179
+ const context = process.context || {};
180
+ const results = process.results || { aggregateData: {} };
181
+ const aggregateData = results.aggregateData || {};
182
+
183
+ await this.websocketService.broadcast({
184
+ type: 'PROCESS_PROGRESS',
185
+ data: {
186
+ processId: process.id,
187
+ processName: process.name,
188
+ processType: process.type,
189
+ state: process.state,
190
+ processed: context.processedRecords || 0,
191
+ total: context.totalRecords || 0,
192
+ successCount: aggregateData.totalSynced || 0,
193
+ errorCount: aggregateData.totalFailed || 0,
194
+ recordsPerSecond: aggregateData.recordsPerSecond || 0,
195
+ estimatedCompletion: context.estimatedCompletion || null,
196
+ timestamp: new Date().toISOString(),
197
+ },
198
+ });
199
+ } catch (error) {
200
+ console.error('Failed to broadcast process progress:', error);
201
+ }
202
+ }
203
+ }
204
+
205
+ module.exports = { UpdateProcessMetrics };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * UpdateProcessState Use Case
3
+ *
4
+ * Updates the state of a process and optionally merges context updates.
5
+ * Handles state transitions in the process state machine.
6
+ *
7
+ * Design Philosophy:
8
+ * - State transitions are explicit and tracked
9
+ * - Context updates are merged (not replaced) to preserve data
10
+ * - Repository handles persistence, use case handles business logic
11
+ *
12
+ * State Machine (CRM Sync Example):
13
+ * INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
14
+ * COMPLETING → COMPLETED
15
+ *
16
+ * Any state can transition to ERROR on failure.
17
+ *
18
+ * @example
19
+ * const updateProcessState = new UpdateProcessState({ processRepository });
20
+ * await updateProcessState.execute(processId, 'FETCHING_TOTAL', {
21
+ * currentPage: 1,
22
+ * pagination: { pageSize: 100 }
23
+ * });
24
+ */
25
+ class UpdateProcessState {
26
+ /**
27
+ * @param {Object} params
28
+ * @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
29
+ */
30
+ constructor({ processRepository }) {
31
+ if (!processRepository) {
32
+ throw new Error('processRepository is required');
33
+ }
34
+ this.processRepository = processRepository;
35
+ }
36
+
37
+ /**
38
+ * Execute the use case to update process state
39
+ * @param {string} processId - Process ID to update
40
+ * @param {string} newState - New state value
41
+ * @param {Object} [contextUpdates={}] - Context fields to merge
42
+ * @returns {Promise<Object>} Updated process record
43
+ * @throws {Error} If process not found or update fails
44
+ */
45
+ async execute(processId, newState, contextUpdates = {}) {
46
+ // Validate inputs
47
+ if (!processId || typeof processId !== 'string') {
48
+ throw new Error('processId must be a non-empty string');
49
+ }
50
+ if (!newState || typeof newState !== 'string') {
51
+ throw new Error('newState must be a non-empty string');
52
+ }
53
+ if (contextUpdates && typeof contextUpdates !== 'object') {
54
+ throw new Error('contextUpdates must be an object');
55
+ }
56
+
57
+ // Route through the atomic path when the repo supports it AND we
58
+ // have context keys to set. The atomic path writes the state
59
+ // column + the context field-sets in one DB round trip without
60
+ // read-modify-write, so a concurrent counter bump from
61
+ // UpdateProcessMetrics can't clobber our flags (e.g. `fetchDone`)
62
+ // or vice versa.
63
+ //
64
+ // Each context update key becomes a `set` at path
65
+ // `context.<key>` — matching the prior semantics of a shallow
66
+ // top-level merge (sub-objects were and still are replaced
67
+ // whole, not deep-merged).
68
+ const hasContextKeys =
69
+ contextUpdates && Object.keys(contextUpdates).length > 0;
70
+
71
+ if (
72
+ hasContextKeys &&
73
+ typeof this.processRepository.applyProcessUpdate === 'function'
74
+ ) {
75
+ const set = {};
76
+ for (const [key, value] of Object.entries(contextUpdates)) {
77
+ set[`context.${key}`] = value;
78
+ }
79
+ try {
80
+ const updated = await this.processRepository.applyProcessUpdate(
81
+ processId,
82
+ { set, newState }
83
+ );
84
+ if (!updated) {
85
+ throw new Error(`Process not found: ${processId}`);
86
+ }
87
+ return updated;
88
+ } catch (error) {
89
+ throw new Error(
90
+ `Failed to update process state: ${error.message}`
91
+ );
92
+ }
93
+ }
94
+
95
+ // Legacy path (no contextUpdates or repo lacks applyProcessUpdate):
96
+ // preserve the original read-merge-write semantics for backward
97
+ // compatibility with any custom repos. Wrap the full read+write
98
+ // in try/catch so a findById error surfaces under the same
99
+ // "Failed to update process state" message as a write failure.
100
+ try {
101
+ const process = await this.processRepository.findById(processId);
102
+ if (!process) {
103
+ throw new Error(`Process not found: ${processId}`);
104
+ }
105
+
106
+ const updates = { state: newState };
107
+ if (hasContextKeys) {
108
+ updates.context = {
109
+ ...process.context,
110
+ ...contextUpdates,
111
+ };
112
+ }
113
+
114
+ return await this.processRepository.update(processId, updates);
115
+ } catch (error) {
116
+ // Re-throw "Process not found" as-is; wrap other errors.
117
+ if (error.message && error.message.startsWith('Process not found')) {
118
+ throw error;
119
+ }
120
+ throw new Error(`Failed to update process state: ${error.message}`);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Helper method to update state without context changes
126
+ * @param {string} processId - Process ID to update
127
+ * @param {string} newState - New state value
128
+ * @returns {Promise<Object>} Updated process record
129
+ */
130
+ async updateStateOnly(processId, newState) {
131
+ return this.execute(processId, newState, {});
132
+ }
133
+
134
+ /**
135
+ * Helper method to update context without changing state
136
+ * @param {string} processId - Process ID to update
137
+ * @param {Object} contextUpdates - Context fields to merge
138
+ * @returns {Promise<Object>} Updated process record
139
+ */
140
+ async updateContextOnly(processId, contextUpdates) {
141
+ const process = await this.processRepository.findById(processId);
142
+ if (!process) {
143
+ throw new Error(`Process not found: ${processId}`);
144
+ }
145
+
146
+ const updates = {
147
+ context: {
148
+ ...process.context,
149
+ ...contextUpdates,
150
+ },
151
+ };
152
+
153
+ return this.processRepository.update(processId, updates);
154
+ }
155
+ }
156
+
157
+ module.exports = { UpdateProcessState };
158
+
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @param {import('../integration').Integration} integration
3
+ * Convert an Integration domain instance to a plain DTO suitable for JSON responses.
4
+ * Can also accept a plain object with an 'options' property to avoid unnecessary instantiation.
5
+ */
6
+ function mapIntegrationClassToIntegrationDTO(integration) {
7
+ if (!integration) return null;
8
+
9
+ return {
10
+ id: integration.id,
11
+ userId: integration.userId,
12
+ entities: integration.entities,
13
+ config: integration.config,
14
+ status: integration.status,
15
+ version: integration.version,
16
+ messages: integration.messages,
17
+ userActions: integration.userActions,
18
+ options: integration.options || (typeof integration.getOptionDetails === 'function' ? integration.getOptionDetails() : null),
19
+ };
20
+ }
21
+
22
+
23
+ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
24
+ return [
25
+ ...new Set(
26
+ integrationClasses
27
+ .map((integration) =>
28
+ Object.values(integration.Definition.modules).map(
29
+ (module) => module.definition
30
+ )
31
+ )
32
+ .flat()
33
+ ),
34
+ ];
35
+ };
36
+
37
+ module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
@@ -0,0 +1,3 @@
1
+ module.exports = async function noopGlobalSetup() {
2
+ // No global setup required for unit tests.
3
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = async function noopGlobalTeardown() {
2
+ // No global teardown required for unit tests.
3
+ };
package/logs/logger.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const util = require('util');
2
- const aws = require('aws-sdk');
3
2
 
4
3
  // Except in some outlier circumstances, for example steam or event error handlers, this should be the only place that calls `console.*`. That way, this file can be modified to log everything properly on a variety of platforms because all the logging code is here in one place.
5
4
  /* eslint-disable no-console */
@@ -7,9 +6,6 @@ const aws = require('aws-sdk');
7
6
  const logs = [];
8
7
  let flushCalled = false;
9
8
 
10
- // Log AWS SDK calls
11
- aws.config.logger = { log: debug };
12
-
13
9
  function debug(...messages) {
14
10
  if (messages.length) {
15
11
  const date = new Date();
@@ -1,25 +1,15 @@
1
- const { Credential } = require('./credential');
2
- const { EntityManager } = require('./entity-manager');
3
- const { Entity } = require('./entity');
4
- const { ModuleManager } = require('./manager');
5
1
  const { ApiKeyRequester } = require('./requester/api-key');
6
2
  const { BasicAuthRequester } = require('./requester/basic');
7
3
  const { OAuth2Requester } = require('./requester/oauth-2');
8
4
  const { Requester } = require('./requester/requester');
9
5
  const { ModuleConstants } = require('./ModuleConstants');
10
6
  const { ModuleFactory } = require('./module-factory');
11
- const { Auther } = require('./auther');
12
7
 
13
8
  module.exports = {
14
- Credential,
15
- EntityManager,
16
- Entity,
17
- ModuleManager,
18
9
  ApiKeyRequester,
19
10
  BasicAuthRequester,
20
11
  OAuth2Requester,
21
12
  Requester,
22
13
  ModuleConstants,
23
14
  ModuleFactory,
24
- Auther
25
15
  };
@@ -0,0 +1,56 @@
1
+ // todo: remove this file
2
+
3
+ const { Module } = require('./module');
4
+
5
+ /**
6
+ * Acts as a factory for fully-hydrated domain Module instances.
7
+ * Provides methods to retrieve and construct Module objects with their associated
8
+ * entity and definition.
9
+ */
10
+ class ModuleFactory {
11
+ /**
12
+ * @param {Object} params - Configuration parameters.
13
+ * @param {import('./repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module data operations.
14
+ * @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
15
+ */
16
+ constructor({ moduleRepository, moduleDefinitions }) {
17
+ this.moduleRepository = moduleRepository;
18
+ this.moduleDefinitions = moduleDefinitions;
19
+ }
20
+
21
+ async getModuleInstance(entityId, userId) {
22
+ const entity = await this.moduleRepository.findEntityById(
23
+ entityId,
24
+ userId
25
+ );
26
+
27
+ if (!entity) {
28
+ throw new Error(`Entity ${entityId} not found`);
29
+ }
30
+
31
+ if (entity.userId !== userId) {
32
+ throw new Error(
33
+ `Entity ${entityId} does not belong to user ${userId}`
34
+ );
35
+ }
36
+
37
+ const moduleName = entity.moduleName;
38
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
39
+ return moduleName === def.moduleName;
40
+ });
41
+
42
+ if (!moduleDefinition) {
43
+ throw new Error(
44
+ `Module definition not found for module: ${moduleName}`
45
+ );
46
+ }
47
+
48
+ return new Module({
49
+ userId,
50
+ entity,
51
+ definition: moduleDefinition,
52
+ });
53
+ }
54
+ }
55
+
56
+ module.exports = { ModuleFactory };