@friggframework/core 2.0.0-next.1 → 2.0.0-next.100

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 (342) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/admin-scripts/index.js +52 -0
  4. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +166 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +228 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +268 -0
  9. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  10. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  11. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  12. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  13. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  14. package/application/commands/README.md +451 -0
  15. package/application/commands/admin-script-commands.js +358 -0
  16. package/application/commands/credential-commands.js +245 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +343 -0
  19. package/application/commands/process-commands.js +135 -0
  20. package/application/commands/scheduler-commands.js +263 -0
  21. package/application/commands/user-commands.js +312 -0
  22. package/application/index.js +82 -0
  23. package/assertions/index.js +0 -3
  24. package/core/CLAUDE.md +690 -0
  25. package/core/Worker.js +60 -24
  26. package/core/create-handler.js +84 -6
  27. package/credential/repositories/credential-repository-documentdb.js +304 -0
  28. package/credential/repositories/credential-repository-factory.js +54 -0
  29. package/credential/repositories/credential-repository-interface.js +98 -0
  30. package/credential/repositories/credential-repository-mongo.js +269 -0
  31. package/credential/repositories/credential-repository-postgres.js +287 -0
  32. package/credential/repositories/credential-repository.js +300 -0
  33. package/credential/use-cases/get-credential-for-user.js +25 -0
  34. package/credential/use-cases/update-authentication-status.js +15 -0
  35. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  36. package/database/adapters/lambda-invoker.js +97 -0
  37. package/database/config.js +154 -0
  38. package/database/documentdb-encryption-service.js +330 -0
  39. package/database/documentdb-utils.js +136 -0
  40. package/database/encryption/README.md +839 -0
  41. package/database/encryption/documentdb-encryption-service.md +3575 -0
  42. package/database/encryption/encryption-schema-registry.js +401 -0
  43. package/database/encryption/field-encryption-service.js +254 -0
  44. package/database/encryption/logger.js +79 -0
  45. package/database/encryption/prisma-encryption-extension.js +230 -0
  46. package/database/index.js +21 -21
  47. package/database/prisma.js +182 -0
  48. package/database/repositories/health-check-repository-documentdb.js +138 -0
  49. package/database/repositories/health-check-repository-factory.js +48 -0
  50. package/database/repositories/health-check-repository-interface.js +82 -0
  51. package/database/repositories/health-check-repository-mongodb.js +89 -0
  52. package/database/repositories/health-check-repository-postgres.js +82 -0
  53. package/database/repositories/migration-status-repository-s3.js +137 -0
  54. package/database/use-cases/check-database-health-use-case.js +29 -0
  55. package/database/use-cases/check-database-state-use-case.js +81 -0
  56. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  57. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  58. package/database/use-cases/get-migration-status-use-case.js +93 -0
  59. package/database/use-cases/run-database-migration-use-case.js +139 -0
  60. package/database/use-cases/test-encryption-use-case.js +253 -0
  61. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  62. package/database/utils/mongodb-collection-utils.js +94 -0
  63. package/database/utils/mongodb-schema-init.js +108 -0
  64. package/database/utils/prisma-runner.js +477 -0
  65. package/database/utils/prisma-schema-parser.js +182 -0
  66. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  67. package/encrypt/Cryptor.js +34 -168
  68. package/encrypt/index.js +1 -2
  69. package/errors/client-safe-error.js +26 -0
  70. package/errors/fetch-error.js +15 -7
  71. package/errors/index.js +2 -0
  72. package/generated/prisma-mongodb/client.d.ts +1 -0
  73. package/generated/prisma-mongodb/client.js +5 -0
  74. package/generated/prisma-mongodb/default.d.ts +1 -0
  75. package/generated/prisma-mongodb/default.js +5 -0
  76. package/generated/prisma-mongodb/edge.d.ts +1 -0
  77. package/generated/prisma-mongodb/edge.js +374 -0
  78. package/generated/prisma-mongodb/index-browser.js +356 -0
  79. package/generated/prisma-mongodb/index.d.ts +25667 -0
  80. package/generated/prisma-mongodb/index.js +399 -0
  81. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  82. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  83. package/generated/prisma-mongodb/package.json +183 -0
  84. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  85. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  86. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  87. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  88. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  89. package/generated/prisma-mongodb/runtime/library.js +147 -0
  90. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  91. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  92. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  93. package/generated/prisma-mongodb/schema.prisma +432 -0
  94. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  95. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  96. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  97. package/generated/prisma-mongodb/wasm.js +381 -0
  98. package/generated/prisma-postgresql/client.d.ts +1 -0
  99. package/generated/prisma-postgresql/client.js +5 -0
  100. package/generated/prisma-postgresql/default.d.ts +1 -0
  101. package/generated/prisma-postgresql/default.js +5 -0
  102. package/generated/prisma-postgresql/edge.d.ts +1 -0
  103. package/generated/prisma-postgresql/edge.js +395 -0
  104. package/generated/prisma-postgresql/index-browser.js +377 -0
  105. package/generated/prisma-postgresql/index.d.ts +28455 -0
  106. package/generated/prisma-postgresql/index.js +420 -0
  107. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  108. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  109. package/generated/prisma-postgresql/package.json +183 -0
  110. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  111. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  112. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  113. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  114. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  115. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  116. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  117. package/generated/prisma-postgresql/runtime/library.js +147 -0
  118. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  119. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  120. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  121. package/generated/prisma-postgresql/schema.prisma +414 -0
  122. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  123. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  124. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  125. package/generated/prisma-postgresql/wasm.js +402 -0
  126. package/handlers/WEBHOOKS.md +653 -0
  127. package/handlers/app-definition-loader.js +43 -0
  128. package/handlers/app-handler-helpers.js +57 -0
  129. package/handlers/backend-utils.js +316 -0
  130. package/handlers/database-migration-handler.js +227 -0
  131. package/handlers/integration-event-dispatcher.js +54 -0
  132. package/handlers/middleware/admin-auth.js +73 -0
  133. package/handlers/routers/HEALTHCHECK.md +342 -0
  134. package/handlers/routers/auth.js +15 -0
  135. package/handlers/routers/db-migration.handler.js +29 -0
  136. package/handlers/routers/db-migration.js +311 -0
  137. package/handlers/routers/health.js +518 -0
  138. package/handlers/routers/integration-defined-routers.js +117 -0
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +63 -0
  142. package/handlers/routers/websocket.js +57 -0
  143. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  144. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  145. package/handlers/workers/db-migration.js +352 -0
  146. package/handlers/workers/dlq-processor.js +63 -0
  147. package/handlers/workers/integration-defined-workers.js +30 -0
  148. package/index.js +111 -50
  149. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  150. package/infrastructure/scheduler/index.js +33 -0
  151. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  152. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  153. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  154. package/integrations/EXTENSIONS.md +240 -0
  155. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  156. package/integrations/extension.js +254 -0
  157. package/integrations/index.js +20 -10
  158. package/integrations/integration-base.js +619 -72
  159. package/integrations/integration-router.js +460 -218
  160. package/integrations/options.js +3 -4
  161. package/integrations/repositories/config-patch-shared.js +43 -0
  162. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  163. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  164. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  165. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  166. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  167. package/integrations/repositories/integration-mapping-repository.js +156 -0
  168. package/integrations/repositories/integration-repository-documentdb.js +284 -0
  169. package/integrations/repositories/integration-repository-factory.js +51 -0
  170. package/integrations/repositories/integration-repository-interface.js +173 -0
  171. package/integrations/repositories/integration-repository-mongo.js +414 -0
  172. package/integrations/repositories/integration-repository-postgres.js +452 -0
  173. package/integrations/repositories/process-repository-documentdb.js +311 -0
  174. package/integrations/repositories/process-repository-factory.js +53 -0
  175. package/integrations/repositories/process-repository-interface.js +136 -0
  176. package/integrations/repositories/process-repository-mongo.js +262 -0
  177. package/integrations/repositories/process-repository-postgres.js +380 -0
  178. package/integrations/repositories/process-update-ops-shared.js +112 -0
  179. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  180. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  181. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  182. package/integrations/use-cases/create-integration.js +215 -0
  183. package/integrations/use-cases/create-process.js +130 -0
  184. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  185. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  186. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  187. package/integrations/use-cases/get-integration-for-user.js +78 -0
  188. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  189. package/integrations/use-cases/get-integration-instance.js +83 -0
  190. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  191. package/integrations/use-cases/get-possible-integrations.js +27 -0
  192. package/integrations/use-cases/get-process.js +89 -0
  193. package/integrations/use-cases/index.js +19 -0
  194. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  195. package/integrations/use-cases/load-integration-context.js +71 -0
  196. package/integrations/use-cases/patch-integration-config.js +39 -0
  197. package/integrations/use-cases/process-errors.js +28 -0
  198. package/integrations/use-cases/update-integration-config.js +32 -0
  199. package/integrations/use-cases/update-integration-messages.js +44 -0
  200. package/integrations/use-cases/update-integration-status.js +32 -0
  201. package/integrations/use-cases/update-integration.js +92 -0
  202. package/integrations/use-cases/update-process-metrics.js +216 -0
  203. package/integrations/use-cases/update-process-state.js +163 -0
  204. package/integrations/utils/map-integration-dto.js +37 -0
  205. package/jest-global-setup-noop.js +3 -0
  206. package/jest-global-teardown-noop.js +3 -0
  207. package/logs/logger.js +0 -4
  208. package/{module-plugin → modules}/index.js +0 -10
  209. package/modules/module-factory.js +56 -0
  210. package/modules/module.js +274 -0
  211. package/modules/repositories/module-repository-documentdb.js +350 -0
  212. package/modules/repositories/module-repository-factory.js +40 -0
  213. package/modules/repositories/module-repository-interface.js +145 -0
  214. package/modules/repositories/module-repository-mongo.js +436 -0
  215. package/modules/repositories/module-repository-postgres.js +481 -0
  216. package/modules/repositories/module-repository.js +369 -0
  217. package/modules/requester/api-key.js +52 -0
  218. package/modules/requester/oauth-2.js +396 -0
  219. package/modules/requester/requester.js +285 -0
  220. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  221. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  222. package/modules/tests/doubles/test-module-factory.js +16 -0
  223. package/modules/tests/doubles/test-module-repository.js +39 -0
  224. package/modules/use-cases/get-entities-for-user.js +32 -0
  225. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  226. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  227. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  228. package/modules/use-cases/get-module.js +74 -0
  229. package/modules/use-cases/process-authorization-callback.js +243 -0
  230. package/modules/use-cases/refresh-entity-options.js +72 -0
  231. package/modules/use-cases/test-module-auth.js +72 -0
  232. package/modules/utils/map-module-dto.js +18 -0
  233. package/package.json +82 -50
  234. package/prisma-mongodb/schema.prisma +432 -0
  235. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  236. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  237. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  238. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  239. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  240. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  241. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  242. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  243. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  244. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  245. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  246. package/prisma-postgresql/schema.prisma +414 -0
  247. package/queues/queuer-util.js +103 -21
  248. package/reporting/README.md +86 -0
  249. package/reporting/index.js +17 -0
  250. package/reporting/reporting-router.js +77 -0
  251. package/reporting/repositories/reporting-repository-documentdb.js +127 -0
  252. package/reporting/repositories/reporting-repository-factory.js +35 -0
  253. package/reporting/repositories/reporting-repository-interface.js +16 -0
  254. package/reporting/repositories/reporting-repository-mongo.js +54 -0
  255. package/reporting/repositories/reporting-repository-postgres.js +70 -0
  256. package/reporting/use-cases/index.js +6 -0
  257. package/reporting/use-cases/list-integrations-report.js +142 -0
  258. package/syncs/manager.js +468 -443
  259. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  260. package/syncs/repositories/sync-repository-factory.js +43 -0
  261. package/syncs/repositories/sync-repository-interface.js +109 -0
  262. package/syncs/repositories/sync-repository-mongo.js +239 -0
  263. package/syncs/repositories/sync-repository-postgres.js +319 -0
  264. package/syncs/sync.js +0 -1
  265. package/token/repositories/token-repository-documentdb.js +137 -0
  266. package/token/repositories/token-repository-factory.js +40 -0
  267. package/token/repositories/token-repository-interface.js +131 -0
  268. package/token/repositories/token-repository-mongo.js +219 -0
  269. package/token/repositories/token-repository-postgres.js +264 -0
  270. package/token/repositories/token-repository.js +219 -0
  271. package/types/associations/index.d.ts +0 -17
  272. package/types/core/index.d.ts +12 -4
  273. package/types/database/index.d.ts +10 -2
  274. package/types/encrypt/index.d.ts +5 -3
  275. package/types/integrations/index.d.ts +3 -8
  276. package/types/module-plugin/index.d.ts +17 -69
  277. package/types/syncs/index.d.ts +0 -17
  278. package/user/repositories/user-repository-documentdb.js +458 -0
  279. package/user/repositories/user-repository-factory.js +52 -0
  280. package/user/repositories/user-repository-interface.js +214 -0
  281. package/user/repositories/user-repository-mongo.js +323 -0
  282. package/user/repositories/user-repository-postgres.js +377 -0
  283. package/user/tests/doubles/test-user-repository.js +72 -0
  284. package/user/use-cases/authenticate-user.js +127 -0
  285. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  286. package/user/use-cases/create-individual-user.js +61 -0
  287. package/user/use-cases/create-organization-user.js +47 -0
  288. package/user/use-cases/create-token-for-user-id.js +30 -0
  289. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  290. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  291. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  292. package/user/use-cases/login-user.js +122 -0
  293. package/user/user.js +125 -0
  294. package/utils/backend-path.js +38 -0
  295. package/utils/index.js +6 -0
  296. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  297. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  298. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  299. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  300. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  301. package/websocket/repositories/websocket-connection-repository.js +161 -0
  302. package/assertions/is-equal.js +0 -17
  303. package/associations/model.js +0 -54
  304. package/database/models/IndividualUser.js +0 -76
  305. package/database/models/OrganizationUser.js +0 -29
  306. package/database/models/State.js +0 -9
  307. package/database/models/Token.js +0 -70
  308. package/database/models/UserModel.js +0 -7
  309. package/database/models/WebsocketConnection.js +0 -49
  310. package/database/mongo.js +0 -45
  311. package/database/mongoose.js +0 -5
  312. package/encrypt/Cryptor.test.js +0 -32
  313. package/encrypt/encrypt.js +0 -132
  314. package/encrypt/encrypt.test.js +0 -1069
  315. package/encrypt/test-encrypt.js +0 -107
  316. package/errors/base-error.test.js +0 -32
  317. package/errors/fetch-error.test.js +0 -79
  318. package/errors/halt-error.test.js +0 -11
  319. package/errors/validation-errors.test.js +0 -120
  320. package/integrations/create-frigg-backend.js +0 -31
  321. package/integrations/integration-factory.js +0 -251
  322. package/integrations/integration-mapping.js +0 -43
  323. package/integrations/integration-model.js +0 -46
  324. package/integrations/integration-user.js +0 -144
  325. package/integrations/test/integration-base.test.js +0 -144
  326. package/lambda/TimeoutCatcher.test.js +0 -68
  327. package/logs/logger.test.js +0 -76
  328. package/module-plugin/auther.js +0 -350
  329. package/module-plugin/credential.js +0 -22
  330. package/module-plugin/entity-manager.js +0 -70
  331. package/module-plugin/entity.js +0 -46
  332. package/module-plugin/manager.js +0 -169
  333. package/module-plugin/module-factory.js +0 -61
  334. package/module-plugin/requester/api-key.js +0 -36
  335. package/module-plugin/requester/oauth-2.js +0 -219
  336. package/module-plugin/requester/requester.js +0 -165
  337. package/module-plugin/requester/requester.test.js +0 -28
  338. package/module-plugin/test/auther.test.js +0 -97
  339. package/syncs/model.js +0 -62
  340. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  341. /package/{module-plugin → modules}/requester/basic.js +0 -0
  342. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Process Commands
3
+ *
4
+ * Application Layer - Command factory for long-running process tracking.
5
+ *
6
+ * Wraps the Process use cases (create, get, update state, update metrics)
7
+ * behind the same `createXCommands()` surface used by the other domains so
8
+ * integration developers can track processes without touching repositories
9
+ * or the underlying ORM directly.
10
+ *
11
+ * @example
12
+ * const processCommands = createProcessCommands();
13
+ * const process = await processCommands.createProcess({
14
+ * userId: 'user-1',
15
+ * integrationId: 'integration-1',
16
+ * name: 'zoho-crm-contact-sync',
17
+ * type: 'CRM_SYNC',
18
+ * });
19
+ * await processCommands.updateProcessState(process.id, 'FETCHING_TOTAL');
20
+ * await processCommands.updateProcessMetrics(process.id, { processed: 100, success: 100 });
21
+ */
22
+
23
+ const {
24
+ createProcessRepository,
25
+ } = require('../../integrations/repositories/process-repository-factory');
26
+ const { CreateProcess } = require('../../integrations/use-cases/create-process');
27
+ const { GetProcess } = require('../../integrations/use-cases/get-process');
28
+ const {
29
+ UpdateProcessState,
30
+ } = require('../../integrations/use-cases/update-process-state');
31
+ const {
32
+ UpdateProcessMetrics,
33
+ } = require('../../integrations/use-cases/update-process-metrics');
34
+
35
+ const ERROR_CODE_MAP = {
36
+ PROCESS_NOT_FOUND: 404,
37
+ INVALID_PROCESS_DATA: 400,
38
+ };
39
+
40
+ function mapErrorToResponse(error) {
41
+ const status = ERROR_CODE_MAP[error?.code] || 500;
42
+ return {
43
+ error: status,
44
+ reason: error?.message,
45
+ code: error?.code,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Create process tracking commands.
51
+ *
52
+ * @param {Object} [params]
53
+ * @param {Object} [params.websocketService] - Optional WebSocket service
54
+ * forwarded to UpdateProcessMetrics for progress broadcasting.
55
+ * @returns {Object} Process commands object
56
+ */
57
+ function createProcessCommands({ websocketService } = {}) {
58
+ const processRepository = createProcessRepository();
59
+
60
+ const createProcessUseCase = new CreateProcess({ processRepository });
61
+ const getProcessUseCase = new GetProcess({ processRepository });
62
+ const updateProcessStateUseCase = new UpdateProcessState({
63
+ processRepository,
64
+ });
65
+ const updateProcessMetricsUseCase = new UpdateProcessMetrics({
66
+ processRepository,
67
+ websocketService,
68
+ });
69
+
70
+ return {
71
+ /**
72
+ * Create a new process record.
73
+ * @param {Object} processData - Process data (userId, integrationId, name, type, ...)
74
+ * @returns {Promise<Object>} Created process record
75
+ */
76
+ async createProcess(processData) {
77
+ try {
78
+ return await createProcessUseCase.execute(processData);
79
+ } catch (error) {
80
+ return mapErrorToResponse(error);
81
+ }
82
+ },
83
+
84
+ /**
85
+ * Retrieve a process by ID.
86
+ * @param {string} processId - Process ID
87
+ * @returns {Promise<Object|null>} Process record, or null if not found
88
+ */
89
+ async getProcess(processId) {
90
+ try {
91
+ return await getProcessUseCase.execute(processId);
92
+ } catch (error) {
93
+ return mapErrorToResponse(error);
94
+ }
95
+ },
96
+
97
+ /**
98
+ * Transition a process to a new state, merging optional context updates.
99
+ * @param {string} processId - Process ID
100
+ * @param {string} newState - New state value
101
+ * @param {Object} [contextUpdates={}] - Context fields to merge
102
+ * @returns {Promise<Object>} Updated process record
103
+ */
104
+ async updateProcessState(processId, newState, contextUpdates = {}) {
105
+ try {
106
+ return await updateProcessStateUseCase.execute(
107
+ processId,
108
+ newState,
109
+ contextUpdates
110
+ );
111
+ } catch (error) {
112
+ return mapErrorToResponse(error);
113
+ }
114
+ },
115
+
116
+ /**
117
+ * Apply a metrics update (counters + bounded error history) to a process.
118
+ * @param {string} processId - Process ID
119
+ * @param {Object} metricsUpdate - Metrics to add (processed, success, errors, skipped, errorDetails)
120
+ * @returns {Promise<Object>} Updated process record
121
+ */
122
+ async updateProcessMetrics(processId, metricsUpdate) {
123
+ try {
124
+ return await updateProcessMetricsUseCase.execute(
125
+ processId,
126
+ metricsUpdate
127
+ );
128
+ } catch (error) {
129
+ return mapErrorToResponse(error);
130
+ }
131
+ },
132
+ };
133
+ }
134
+
135
+ module.exports = { createProcessCommands };
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Scheduler Commands
3
+ *
4
+ * Application Layer - Command pattern for scheduling operations.
5
+ *
6
+ * Follows hexagonal architecture:
7
+ * - Receives SchedulerServiceInterface via dependency injection
8
+ * - Contains business logic (validation, logging, error mapping)
9
+ * - Protocol-agnostic (doesn't know about HTTP/Lambda)
10
+ *
11
+ * @example
12
+ * const schedulerCommands = createSchedulerCommands({ integrationName: 'zoho' });
13
+ * await schedulerCommands.scheduleJob({
14
+ * jobId: 'zoho-notif-renewal-abc123',
15
+ * scheduledAt: new Date(Date.now() + 6 * 24 * 60 * 60 * 1000), // 6 days
16
+ * event: 'REFRESH_WEBHOOK',
17
+ * payload: { integrationId: 'abc123' },
18
+ * queueUrl: process.env.ZOHO_QUEUE_URL,
19
+ * });
20
+ */
21
+
22
+ const { createSchedulerService } = require('../../infrastructure/scheduler');
23
+
24
+ /**
25
+ * Derive SQS ARN from SQS URL
26
+ *
27
+ * SQS URL format: https://sqs.{region}.amazonaws.com/{account-id}/{queue-name}
28
+ * SQS ARN format: arn:aws:sqs:{region}:{account-id}:{queue-name}
29
+ *
30
+ * @param {string} queueUrl - SQS queue URL
31
+ * @returns {string} SQS queue ARN
32
+ */
33
+ function deriveArnFromQueueUrl(queueUrl) {
34
+ try {
35
+ const url = new URL(queueUrl);
36
+ const region = url.hostname.split('.')[1];
37
+ const pathParts = url.pathname.split('/').filter(Boolean);
38
+ const accountId = pathParts[0];
39
+ const queueName = pathParts[1];
40
+ return `arn:aws:sqs:${region}:${accountId}:${queueName}`;
41
+ } catch (error) {
42
+ throw new Error(`Invalid SQS queue URL: ${queueUrl}`);
43
+ }
44
+ }
45
+
46
+ const ERROR_CODE_MAP = {
47
+ SCHEDULER_NOT_CONFIGURED: 503,
48
+ INVALID_JOB_DATA: 400,
49
+ SCHEDULE_NOT_FOUND: 404,
50
+ };
51
+
52
+ function mapErrorToResponse(error) {
53
+ const status = ERROR_CODE_MAP[error?.code] || 500;
54
+ return {
55
+ error: status,
56
+ reason: error?.message,
57
+ code: error?.code,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Create scheduler commands for an integration
63
+ *
64
+ * @param {Object} params
65
+ * @param {string} params.integrationName - Name of the integration (used for logging)
66
+ * @param {SchedulerServiceInterface} [params.schedulerService] - Optional injected scheduler service
67
+ * @returns {Object} Scheduler commands object
68
+ */
69
+ function createSchedulerCommands({ integrationName, schedulerService }) {
70
+ if (!integrationName) {
71
+ throw new Error('integrationName is required');
72
+ }
73
+
74
+ // Support both dependency injection and lazy creation
75
+ // DI is preferred for testability, lazy creation for convenience
76
+ let _schedulerService = schedulerService || null;
77
+
78
+ function getSchedulerService() {
79
+ if (!_schedulerService) {
80
+ try {
81
+ _schedulerService = createSchedulerService();
82
+ } catch (error) {
83
+ console.warn(
84
+ `[${integrationName}] Scheduler service not available: ${error.message}`
85
+ );
86
+ return null;
87
+ }
88
+ }
89
+ return _schedulerService;
90
+ }
91
+
92
+ return {
93
+ /**
94
+ * Schedule a one-time job to be executed at a specific time
95
+ *
96
+ * @param {Object} params
97
+ * @param {string} params.jobId - Unique identifier for the job
98
+ * @param {Date} params.scheduledAt - When to execute the job
99
+ * @param {string} params.event - Event name to trigger
100
+ * @param {Object} params.payload - Additional payload data
101
+ * @param {string} params.queueUrl - Target SQS queue URL (ARN is derived internally)
102
+ * @returns {Promise<{jobArn: string, scheduledAt: string} | {error: number, reason: string}>}
103
+ */
104
+ async scheduleJob({ jobId, scheduledAt, event, payload, queueUrl }) {
105
+ try {
106
+ if (!jobId) {
107
+ const error = new Error('jobId is required');
108
+ error.code = 'INVALID_JOB_DATA';
109
+ throw error;
110
+ }
111
+
112
+ if (!scheduledAt || !(scheduledAt instanceof Date)) {
113
+ const error = new Error('scheduledAt must be a valid Date');
114
+ error.code = 'INVALID_JOB_DATA';
115
+ throw error;
116
+ }
117
+
118
+ if (!event) {
119
+ const error = new Error('event is required');
120
+ error.code = 'INVALID_JOB_DATA';
121
+ throw error;
122
+ }
123
+
124
+ if (!queueUrl) {
125
+ const error = new Error('queueUrl is required');
126
+ error.code = 'INVALID_JOB_DATA';
127
+ throw error;
128
+ }
129
+
130
+ // Derive ARN from URL (business logic - transformation)
131
+ const queueArn = deriveArnFromQueueUrl(queueUrl);
132
+
133
+ // Get scheduler service (via DI or factory)
134
+ const service = getSchedulerService();
135
+ if (!service) {
136
+ console.warn(
137
+ `[${integrationName}] Scheduler not configured, skipping job schedule`
138
+ );
139
+ return {
140
+ jobId,
141
+ jobArn: null,
142
+ scheduledAt: null,
143
+ warning: 'Scheduler not configured',
144
+ };
145
+ }
146
+
147
+ // Build the SQS message payload (business logic - assembly)
148
+ const sqsPayload = {
149
+ event,
150
+ integrationName,
151
+ data: payload || {},
152
+ scheduledAt: scheduledAt.toISOString(),
153
+ createdAt: new Date().toISOString(),
154
+ };
155
+
156
+ // Delegate to service (Port interface)
157
+ const result = await service.scheduleOneTime({
158
+ scheduleName: jobId,
159
+ scheduleAt: scheduledAt,
160
+ queueResourceId: queueArn,
161
+ payload: sqsPayload,
162
+ });
163
+
164
+ console.log(
165
+ `[${integrationName}] Scheduled job ${jobId} for ${result.scheduledAt}`
166
+ );
167
+
168
+ return {
169
+ jobId,
170
+ jobArn: result.scheduledJobId,
171
+ scheduledAt: result.scheduledAt,
172
+ };
173
+ } catch (error) {
174
+ console.error(
175
+ `[${integrationName}] Failed to schedule job ${jobId}:`,
176
+ error.message
177
+ );
178
+ return mapErrorToResponse(error);
179
+ }
180
+ },
181
+
182
+ /**
183
+ * Delete a scheduled job
184
+ *
185
+ * @param {string} jobId - Job ID to delete
186
+ * @returns {Promise<{success: boolean, jobId: string} | {error: number, reason: string}>}
187
+ */
188
+ async deleteJob(jobId) {
189
+ try {
190
+ if (!jobId) {
191
+ const error = new Error('jobId is required');
192
+ error.code = 'INVALID_JOB_DATA';
193
+ throw error;
194
+ }
195
+
196
+ const service = getSchedulerService();
197
+ if (!service) {
198
+ console.warn(
199
+ `[${integrationName}] Scheduler not configured, skipping job deletion`
200
+ );
201
+ return {
202
+ success: true,
203
+ jobId,
204
+ warning: 'Scheduler not configured',
205
+ };
206
+ }
207
+
208
+ await service.deleteSchedule(jobId);
209
+
210
+ console.log(`[${integrationName}] Deleted scheduled job ${jobId}`);
211
+
212
+ return {
213
+ success: true,
214
+ jobId,
215
+ };
216
+ } catch (error) {
217
+ console.error(
218
+ `[${integrationName}] Failed to delete job ${jobId}:`,
219
+ error.message
220
+ );
221
+ return mapErrorToResponse(error);
222
+ }
223
+ },
224
+
225
+ /**
226
+ * Get the status of a scheduled job
227
+ *
228
+ * @param {string} jobId - Job ID to check
229
+ * @returns {Promise<{exists: boolean, scheduledAt?: string, state?: string} | {error: number, reason: string}>}
230
+ */
231
+ async getJobStatus(jobId) {
232
+ try {
233
+ if (!jobId) {
234
+ const error = new Error('jobId is required');
235
+ error.code = 'INVALID_JOB_DATA';
236
+ throw error;
237
+ }
238
+
239
+ const service = getSchedulerService();
240
+ if (!service) {
241
+ return {
242
+ exists: false,
243
+ warning: 'Scheduler not configured',
244
+ };
245
+ }
246
+
247
+ const status = await service.getScheduleStatus(jobId);
248
+
249
+ return status;
250
+ } catch (error) {
251
+ console.error(
252
+ `[${integrationName}] Failed to get job status ${jobId}:`,
253
+ error.message
254
+ );
255
+ return mapErrorToResponse(error);
256
+ }
257
+ },
258
+ };
259
+ }
260
+
261
+ module.exports = {
262
+ createSchedulerCommands,
263
+ };
@@ -0,0 +1,312 @@
1
+ const {
2
+ createUserRepository,
3
+ } = require('../../user/repositories/user-repository-factory');
4
+
5
+ const ERROR_CODE_MAP = {
6
+ USER_NOT_FOUND: 404,
7
+ USER_ALREADY_EXISTS: 409,
8
+ INVALID_USER_DATA: 400,
9
+ };
10
+
11
+ function mapErrorToResponse(error) {
12
+ const status = ERROR_CODE_MAP[error?.code] || 500;
13
+ return {
14
+ error: status,
15
+ reason: error?.message,
16
+ code: error?.code,
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Create user command factory
22
+ *
23
+ * NOTE: This is an internal API. Integration developers should use createFriggCommands() instead.
24
+ *
25
+ * @returns {Object} User command object with CRUD operations
26
+ */
27
+ function createUserCommands() {
28
+ const userRepository = createUserRepository();
29
+
30
+ return {
31
+ /**
32
+ * Create a new individual user
33
+ * @param {Object} params
34
+ * @param {string} params.username - Username (usually email)
35
+ * @param {string} [params.email] - Email address
36
+ * @param {string} [params.appUserId] - External application user ID
37
+ * @param {string} [params.password] - Password (optional)
38
+ * @returns {Promise<Object>} Created user object
39
+ */
40
+ async createUser({ username, email, appUserId, password } = {}) {
41
+ try {
42
+ if (!username) {
43
+ const error = new Error('username is required');
44
+ error.code = 'INVALID_USER_DATA';
45
+ throw error;
46
+ }
47
+
48
+ const userData = { username };
49
+ if (email) userData.email = email;
50
+ if (appUserId) userData.appUserId = appUserId;
51
+ if (password) userData.password = password;
52
+
53
+ const user = await userRepository.createIndividualUser(
54
+ userData
55
+ );
56
+
57
+ return {
58
+ id: user.id,
59
+ username: user.username,
60
+ email: user.email,
61
+ appUserId: user.appUserId,
62
+ };
63
+ } catch (error) {
64
+ if (error.code === 11000) {
65
+ // Duplicate key error
66
+ const duplicateError = new Error(
67
+ `User with username '${username}' already exists`
68
+ );
69
+ duplicateError.code = 'USER_ALREADY_EXISTS';
70
+ return mapErrorToResponse(duplicateError);
71
+ }
72
+ return mapErrorToResponse(error);
73
+ }
74
+ },
75
+
76
+ /**
77
+ * Find a user by their application user ID
78
+ * @param {string} appUserId - External application user ID
79
+ * @returns {Promise<Object|null>} User object or null if not found
80
+ */
81
+ async findUserByAppUserId(appUserId) {
82
+ try {
83
+ if (!appUserId) {
84
+ const error = new Error('appUserId is required');
85
+ error.code = 'INVALID_USER_DATA';
86
+ throw error;
87
+ }
88
+
89
+ const user = await userRepository.findIndividualUserByAppUserId(
90
+ appUserId
91
+ );
92
+
93
+ if (!user) {
94
+ return null;
95
+ }
96
+
97
+ return {
98
+ id: user.id,
99
+ username: user.username,
100
+ email: user.email,
101
+ appUserId: user.appUserId,
102
+ };
103
+ } catch (error) {
104
+ return mapErrorToResponse(error);
105
+ }
106
+ },
107
+
108
+ /**
109
+ * Find a user by their username
110
+ * @param {string} username - Username to search for
111
+ * @returns {Promise<Object|null>} User object or null if not found
112
+ */
113
+ async findUserByUsername(username) {
114
+ try {
115
+ if (!username) {
116
+ const error = new Error('username is required');
117
+ error.code = 'INVALID_USER_DATA';
118
+ throw error;
119
+ }
120
+
121
+ const user = await userRepository.findIndividualUserByUsername(
122
+ username
123
+ );
124
+
125
+ if (!user) {
126
+ return null;
127
+ }
128
+
129
+ return {
130
+ id: user.id,
131
+ username: user.username,
132
+ email: user.email,
133
+ appUserId: user.appUserId,
134
+ };
135
+ } catch (error) {
136
+ return mapErrorToResponse(error);
137
+ }
138
+ },
139
+
140
+ /**
141
+ * Find an individual user by their ID
142
+ * @param {string} userId - Individual user ID to search for
143
+ * @returns {Promise<Object|null>} Individual user object or null if not found
144
+ */
145
+ async findIndividualUserById(userId) {
146
+ try {
147
+ if (!userId) {
148
+ const error = new Error('userId is required');
149
+ error.code = 'INVALID_USER_DATA';
150
+ throw error;
151
+ }
152
+
153
+ const user = await userRepository.findIndividualUserById(
154
+ userId
155
+ );
156
+
157
+ if (!user) {
158
+ return null;
159
+ }
160
+
161
+ return {
162
+ id: user._id?.toString() || user.id,
163
+ username: user.username,
164
+ email: user.email,
165
+ appUserId: user.appUserId,
166
+ };
167
+ } catch (error) {
168
+ return mapErrorToResponse(error);
169
+ }
170
+ },
171
+
172
+ /**
173
+ * Find all individual users linked to an organization user
174
+ * @param {string} organizationUserId - Organization user ID to search for
175
+ * @returns {Promise<Object[]>} Array of individual user objects (empty if none)
176
+ */
177
+ async findIndividualUsersByOrganizationId(organizationUserId) {
178
+ try {
179
+ if (!organizationUserId) {
180
+ const error = new Error('organizationUserId is required');
181
+ error.code = 'INVALID_USER_DATA';
182
+ throw error;
183
+ }
184
+
185
+ const users =
186
+ await userRepository.findIndividualUsersByOrganizationId(
187
+ organizationUserId
188
+ );
189
+
190
+ return (users || []).map((user) => ({
191
+ id: user._id?.toString() || user.id,
192
+ username: user.username,
193
+ email: user.email,
194
+ appUserId: user.appUserId,
195
+ }));
196
+ } catch (error) {
197
+ return mapErrorToResponse(error);
198
+ }
199
+ },
200
+
201
+ /**
202
+ * Find an organization user by their ID
203
+ * @param {string} userId - Organization user ID to search for
204
+ * @returns {Promise<Object|null>} Organization user object or null if not found
205
+ */
206
+ async findOrganizationUserById(userId) {
207
+ try {
208
+ if (!userId) {
209
+ const error = new Error('userId is required');
210
+ error.code = 'INVALID_USER_DATA';
211
+ throw error;
212
+ }
213
+
214
+ const user = await userRepository.findOrganizationUserById(
215
+ userId
216
+ );
217
+
218
+ if (!user) {
219
+ return null;
220
+ }
221
+
222
+ return {
223
+ id: user.id,
224
+ appOrgId: user.appOrgId,
225
+ name: user.name,
226
+ };
227
+ } catch (error) {
228
+ return mapErrorToResponse(error);
229
+ }
230
+ },
231
+
232
+ /**
233
+ * Update a user by ID
234
+ * @param {string} userId - User ID to update
235
+ * @param {Object} updates - Fields to update
236
+ * @returns {Promise<Object>} Updated user object
237
+ */
238
+ async updateUser(userId, updates) {
239
+ try {
240
+ if (!userId) {
241
+ const error = new Error('userId is required');
242
+ error.code = 'INVALID_USER_DATA';
243
+ throw error;
244
+ }
245
+
246
+ const user = await userRepository.IndividualUser.update(
247
+ userId,
248
+ updates
249
+ );
250
+
251
+ if (!user) {
252
+ const error = new Error(`User ${userId} not found`);
253
+ error.code = 'USER_NOT_FOUND';
254
+ throw error;
255
+ }
256
+
257
+ return {
258
+ id: user._id.toString(),
259
+ username: user.username,
260
+ email: user.email,
261
+ appUserId: user.appUserId,
262
+ };
263
+ } catch (error) {
264
+ return mapErrorToResponse(error);
265
+ }
266
+ },
267
+
268
+ /**
269
+ * Delete a user by ID
270
+ *
271
+ * IMPORTANT: This does NOT automatically cascade delete related records in MongoDB.
272
+ * Integration developers MUST manually delete related data first:
273
+ * 1. Delete integrations (via deleteIntegrationById)
274
+ * 2. Delete entities (via deleteEntityById)
275
+ * 3. Delete credentials (via deleteCredentialById)
276
+ * 4. Finally delete user (via deleteUserById)
277
+ *
278
+ * @param {string} userId - User ID to delete
279
+ * @returns {Promise<Object>} Deletion result
280
+ */
281
+ async deleteUserById(userId) {
282
+ try {
283
+ if (!userId) {
284
+ const error = new Error('userId is required');
285
+ error.code = 'INVALID_USER_DATA';
286
+ throw error;
287
+ }
288
+
289
+ const deleted = await userRepository.deleteUser(userId);
290
+
291
+ if (!deleted) {
292
+ const error = new Error(`User ${userId} not found`);
293
+ error.code = 'USER_NOT_FOUND';
294
+ return mapErrorToResponse(error);
295
+ }
296
+
297
+ return {
298
+ success: true,
299
+ userId,
300
+ message: 'User deleted successfully',
301
+ };
302
+ } catch (error) {
303
+ return mapErrorToResponse(error);
304
+ }
305
+ },
306
+ };
307
+ }
308
+
309
+ module.exports = {
310
+ createUserCommands,
311
+ ERROR_CODE_MAP,
312
+ };