@friggframework/core 2.0.0-next.10 → 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 (344) 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 +3 -3
  129. package/handlers/backend-utils.js +271 -42
  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 +4 -15
  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 +85 -10
  139. package/handlers/routers/integration-webhook-routers.js +67 -0
  140. package/handlers/routers/reporting.js +9 -0
  141. package/handlers/routers/user.js +27 -5
  142. package/handlers/routers/websocket.js +5 -3
  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 +13 -7
  148. package/index.js +91 -46
  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 +398 -180
  160. package/integrations/options.js +1 -1
  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/handlers/routers/middleware/loadUser.js +0 -15
  321. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  322. package/integrations/create-frigg-backend.js +0 -31
  323. package/integrations/integration-factory.js +0 -251
  324. package/integrations/integration-mapping.js +0 -43
  325. package/integrations/integration-model.js +0 -46
  326. package/integrations/integration-user.js +0 -144
  327. package/integrations/test/integration-base.test.js +0 -144
  328. package/lambda/TimeoutCatcher.test.js +0 -68
  329. package/logs/logger.test.js +0 -76
  330. package/module-plugin/auther.js +0 -393
  331. package/module-plugin/credential.js +0 -22
  332. package/module-plugin/entity-manager.js +0 -70
  333. package/module-plugin/entity.js +0 -46
  334. package/module-plugin/manager.js +0 -169
  335. package/module-plugin/module-factory.js +0 -61
  336. package/module-plugin/requester/api-key.js +0 -36
  337. package/module-plugin/requester/oauth-2.js +0 -219
  338. package/module-plugin/requester/requester.js +0 -165
  339. package/module-plugin/requester/requester.test.js +0 -28
  340. package/module-plugin/test/auther.test.js +0 -97
  341. package/syncs/model.js +0 -62
  342. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  343. /package/{module-plugin → modules}/requester/basic.js +0 -0
  344. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -3,7 +3,6 @@ const express = require('express');
3
3
  const bodyParser = require('body-parser');
4
4
  const cors = require('cors');
5
5
  const Boom = require('@hapi/boom');
6
- const loadUserManager = require('./routers/middleware/loadUser');
7
6
  const serverlessHttp = require('serverless-http');
8
7
 
9
8
  const createApp = (applyMiddleware) => {
@@ -14,12 +13,12 @@ const createApp = (applyMiddleware) => {
14
13
  app.use(
15
14
  cors({
16
15
  origin: '*',
16
+ allowedHeaders: '*',
17
+ methods: '*',
17
18
  credentials: true,
18
19
  })
19
20
  );
20
21
 
21
- app.use(loadUserManager);
22
-
23
22
  if (applyMiddleware) applyMiddleware(app);
24
23
 
25
24
  // Handle sending error response and logging server errors to console
@@ -33,6 +32,7 @@ const createApp = (applyMiddleware) => {
33
32
  flushDebugLog(boomError);
34
33
  res.status(statusCode).json({ error: 'Internal Server Error' });
35
34
  } else {
35
+ console.warn(`[Frigg] ${req.method} ${req.path} -> ${statusCode}: ${err.message}`);
36
36
  res.status(statusCode).json({ error: err.message });
37
37
  }
38
38
  });
@@ -1,38 +1,48 @@
1
- const { createFriggBackend, Worker } = require('@friggframework/core');
2
- const {
3
- findNearestBackendPackageJson,
4
- } = require('../../devtools/frigg-cli/utils/backend-path');
5
- const path = require('node:path');
6
- const fs = require('fs-extra');
7
-
8
- const backendPath = findNearestBackendPackageJson();
9
- if (!backendPath) {
10
- throw new Error('Could not find backend package.json');
11
- }
12
-
13
- const backendDir = path.dirname(backendPath);
14
- const backendFilePath = path.join(backendDir, 'index.js');
15
- if (!fs.existsSync(backendFilePath)) {
16
- throw new Error('Could not find index.js');
17
- }
18
-
19
- const backendJsFile = require(backendFilePath);
20
1
  const { Router } = require('express');
21
- const appDefinition = backendJsFile.Definition;
2
+ const { Worker } = require('@friggframework/core');
3
+ const {
4
+ IntegrationEventDispatcher,
5
+ } = require('./integration-event-dispatcher');
6
+ const {
7
+ GetIntegrationInstance,
8
+ } = require('../integrations/use-cases/get-integration-instance');
9
+ const { ModuleFactory } = require('../modules/module-factory');
10
+ const {
11
+ createProcessRepository,
12
+ } = require('../integrations/repositories/process-repository-factory');
13
+ const {
14
+ createIntegrationRepository,
15
+ } = require('../integrations/repositories/integration-repository-factory');
16
+ const {
17
+ createModuleRepository,
18
+ } = require('../modules/repositories/module-repository-factory');
19
+ const {
20
+ getModulesDefinitionFromIntegrationClasses,
21
+ } = require('../integrations/utils/map-integration-dto');
22
22
 
23
- const backend = createFriggBackend(appDefinition);
24
23
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
25
24
  const router = Router();
26
25
  const { path, method, event } = routerObject;
26
+
27
27
  console.log(
28
28
  `Registering ${method} ${path} for ${IntegrationClass.Definition.name}`
29
29
  );
30
+
30
31
  router[method.toLowerCase()](path, async (req, res, next) => {
31
32
  try {
32
- const integration = new IntegrationClass({});
33
- await integration.loadModules();
34
- await integration.registerEventHandlers();
35
- const result = await integration.send(event, {req, res, next});
33
+ const integrationInstance = new IntegrationClass();
34
+ // initialize() registers dynamic user actions AND merges any Tier 3
35
+ // Integration Extension events into instance.events before dispatch.
36
+ await integrationInstance.initialize();
37
+ const dispatcher = new IntegrationEventDispatcher(
38
+ integrationInstance
39
+ );
40
+ const result = await dispatcher.dispatchHttp({
41
+ event,
42
+ req,
43
+ res,
44
+ next,
45
+ });
36
46
  res.json(result);
37
47
  } catch (error) {
38
48
  next(error);
@@ -41,38 +51,256 @@ const loadRouterFromObject = (IntegrationClass, routerObject) => {
41
51
 
42
52
  return router;
43
53
  };
54
+
55
+ const initializeRepositories = () => {
56
+ const processRepository = createProcessRepository();
57
+ const integrationRepository = createIntegrationRepository();
58
+ const moduleRepository = createModuleRepository();
59
+
60
+ return { processRepository, integrationRepository, moduleRepository };
61
+ };
62
+
63
+ const createModuleFactoryWithDefinitions = (
64
+ moduleRepository,
65
+ integrationClasses
66
+ ) => {
67
+ const moduleDefinitions =
68
+ getModulesDefinitionFromIntegrationClasses(integrationClasses);
69
+
70
+ return new ModuleFactory({
71
+ moduleRepository,
72
+ moduleDefinitions,
73
+ });
74
+ };
75
+
76
+ const loadIntegrationForWebhook = async (integrationId) => {
77
+ const { loadAppDefinition } = require('./app-definition-loader');
78
+ const { integrations: integrationClasses } = loadAppDefinition();
79
+
80
+ const { integrationRepository, moduleRepository } =
81
+ initializeRepositories();
82
+
83
+ const moduleFactory = createModuleFactoryWithDefinitions(
84
+ moduleRepository,
85
+ integrationClasses
86
+ );
87
+
88
+ const getIntegrationInstance = new GetIntegrationInstance({
89
+ integrationRepository,
90
+ integrationClasses,
91
+ moduleFactory,
92
+ });
93
+
94
+ let integrationRecord;
95
+ try {
96
+ integrationRecord = await integrationRepository.findIntegrationById(
97
+ integrationId
98
+ );
99
+ } catch (error) {
100
+ if (error.message?.includes('not found')) {
101
+ return null;
102
+ }
103
+ throw error;
104
+ }
105
+
106
+ const instance = await getIntegrationInstance.execute(
107
+ integrationId,
108
+ integrationRecord.userId
109
+ );
110
+
111
+ return instance;
112
+ };
113
+
114
+ // Returns false only when the integration is confirmed gone; a transient
115
+ // lookup failure returns true so genuine errors keep their normal retry path.
116
+ const integrationExists = async (integrationId) => {
117
+ const integrationRepository = createIntegrationRepository();
118
+ try {
119
+ const record = await integrationRepository.findIntegrationById(
120
+ integrationId
121
+ );
122
+ return Boolean(record);
123
+ } catch (error) {
124
+ if (error.message?.includes('not found')) {
125
+ return false;
126
+ }
127
+ return true;
128
+ }
129
+ };
130
+
131
+ const loadIntegrationForProcess = async (processId, integrationClass) => {
132
+ const { processRepository, integrationRepository, moduleRepository } =
133
+ initializeRepositories();
134
+
135
+ const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
136
+ integrationClass,
137
+ ]);
138
+
139
+ const getIntegrationInstance = new GetIntegrationInstance({
140
+ integrationRepository,
141
+ integrationClasses: [integrationClass],
142
+ moduleFactory,
143
+ });
144
+
145
+ if (!processId) {
146
+ throw new Error('processId is required in queue message data');
147
+ }
148
+
149
+ const process = await processRepository.findById(processId);
150
+
151
+ if (!process) {
152
+ throw new Error(`Process not found: ${processId}`);
153
+ }
154
+
155
+ const instance = await getIntegrationInstance.execute(
156
+ process.integrationId,
157
+ process.userId
158
+ );
159
+
160
+ return instance;
161
+ };
162
+
44
163
  const createQueueWorker = (integrationClass) => {
164
+ const integrationName = integrationClass.Definition.name;
165
+
45
166
  class QueueWorker extends Worker {
46
167
  async _run(params, context) {
168
+ const logCtx = {
169
+ integration: integrationName,
170
+ event: params.event,
171
+ processId: params.data?.processId,
172
+ integrationId: params.data?.integrationId,
173
+ };
174
+
47
175
  try {
48
- let instance;
49
- if (!params.integrationId) {
50
- instance = new integrationClass({});
51
- await instance.loadModules();
52
- // await instance.loadUserActions();
53
- await instance.registerEventHandlers();
176
+ let integrationInstance;
177
+
178
+ // Prioritize processId first (for sync handler compatibility),
179
+ // then integrationId (for ANY event type that needs hydration),
180
+ // fallback to unhydrated instance
181
+ if (params.data?.processId) {
182
+ console.log(`[QueueWorker] hydrating by processId`, logCtx);
183
+ integrationInstance = await loadIntegrationForProcess(
184
+ params.data.processId,
185
+ integrationClass
186
+ );
187
+ console.log(`[QueueWorker] hydrated`, {
188
+ ...logCtx,
189
+ integrationStatus: integrationInstance?.status,
190
+ hydratedIntegrationId: integrationInstance?.id,
191
+ });
192
+ if (
193
+ ['DISABLED', 'ERROR', 'IN_DELETION'].includes(
194
+ integrationInstance?.status
195
+ )
196
+ ) {
197
+ console.warn(
198
+ `[${integrationName}] Integration for process ${params.data.processId} is ${integrationInstance.status}. Discarding ${params.event} message.`
199
+ );
200
+ return;
201
+ }
202
+ } else if (params.data?.integrationId) {
54
203
  console.log(
55
- `${params.event} for ${integrationClass.Definition.name} integration with no integrationId`
204
+ `[QueueWorker] hydrating by integrationId`,
205
+ logCtx
206
+ );
207
+ integrationInstance = await loadIntegrationForWebhook(
208
+ params.data.integrationId
56
209
  );
210
+ if (!integrationInstance) {
211
+ console.warn(
212
+ `[${integrationName}] Integration ${params.data.integrationId} no longer exists. Discarding ${params.event} message.`
213
+ );
214
+ return;
215
+ }
216
+ console.log(`[QueueWorker] hydrated`, {
217
+ ...logCtx,
218
+ integrationStatus: integrationInstance?.status,
219
+ });
220
+ if (
221
+ ['DISABLED', 'ERROR', 'IN_DELETION'].includes(
222
+ integrationInstance.status
223
+ )
224
+ ) {
225
+ console.warn(
226
+ `[${integrationName}] Integration ${params.data.integrationId} is ${integrationInstance.status}. Discarding ${params.event} message.`
227
+ );
228
+ return;
229
+ }
57
230
  } else {
58
- instance =
59
- await integrationClass.getInstanceFromIntegrationId({
60
- integrationId: params.integrationId,
61
- });
231
+ // Instantiates a DRY integration class without database records.
232
+ // There will be cases where we need to use helpers that the api modules can export.
233
+ // Like for HubSpot, the answer is to do a reverse lookup for the integration by the entity external ID (HubSpot Portal ID),
234
+ // and then you'll have the integration ID available to hydrate from.
62
235
  console.log(
63
- `${params.event} for ${instance.integration.config.type} of integrationId: ${params.integrationId}`
236
+ `[QueueWorker] no processId/integrationId running dry instance`,
237
+ logCtx
64
238
  );
239
+ integrationInstance = new integrationClass();
240
+ // Merge Tier 3 Integration Extension events into instance.events
241
+ // so extension-contributed queue events can be dispatched.
242
+ await integrationInstance.initialize();
65
243
  }
66
- const res = await instance.send(params.event, {
244
+
245
+ const dispatcher = new IntegrationEventDispatcher(
246
+ integrationInstance
247
+ );
248
+
249
+ console.log(
250
+ `[QueueWorker] dispatching ${params.event}`,
251
+ logCtx
252
+ );
253
+ const result = await dispatcher.dispatchJob({
254
+ event: params.event,
67
255
  data: params.data,
68
- context,
256
+ context: context,
69
257
  });
70
- return res;
258
+ console.log(
259
+ `[QueueWorker] ${params.event} dispatched ok`,
260
+ logCtx
261
+ );
262
+ return result;
71
263
  } catch (error) {
264
+ // Integration deleted mid-flight: no retry can succeed once
265
+ // it's gone, so discard instead of sending it to the DLQ.
266
+ if (
267
+ params.data?.integrationId &&
268
+ !(await integrationExists(params.data.integrationId))
269
+ ) {
270
+ console.warn(
271
+ `[${integrationName}] Integration ${params.data.integrationId} was deleted mid-flight — discarding ${params.event} message (no retry)`
272
+ );
273
+ return;
274
+ }
275
+
72
276
  console.error(
73
- `Error in ${params.event} for ${integrationClass.Definition.name}:`,
277
+ `Error in ${params.event} for ${integrationName}:`,
74
278
  error
75
279
  );
280
+
281
+ // 4xx HTTP errors are permanent — the requester already
282
+ // attempted token refresh (401) and backoff (429/5xx).
283
+ // By the time a 4xx reaches here, retrying won't help.
284
+ // 408 (timeout) and 429 (rate limit) are excluded — both are transient.
285
+ const status = error.statusCode;
286
+ if (
287
+ status &&
288
+ status >= 400 &&
289
+ status < 500 &&
290
+ status !== 408 &&
291
+ status !== 429
292
+ ) {
293
+ error.isHaltError = true;
294
+ console.warn(
295
+ `[${integrationName}] Permanent ${status} error for ${params.event} — message will be discarded (no retry)`,
296
+ {
297
+ ...logCtx,
298
+ errorName: error.name,
299
+ errorMessage: error.message,
300
+ }
301
+ );
302
+ }
303
+
76
304
  throw error;
77
305
  }
78
306
  }
@@ -81,7 +309,8 @@ const createQueueWorker = (integrationClass) => {
81
309
  };
82
310
 
83
311
  module.exports = {
84
- ...backend,
85
312
  loadRouterFromObject,
86
313
  createQueueWorker,
314
+ integrationExists,
315
+ loadIntegrationForWebhook,
87
316
  };
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Database Migration Handler for AWS Lambda
3
+ *
4
+ * Executes Prisma migrations in a Lambda environment.
5
+ * Based on AWS best practices for running migrations in serverless environments.
6
+ *
7
+ * Supported Commands:
8
+ * - deploy: Apply pending migrations to the database (production-safe)
9
+ * - reset: Reset database and apply all migrations (DANGEROUS - dev only)
10
+ *
11
+ * Usage:
12
+ * // Via Lambda invoke
13
+ * {
14
+ * "command": "deploy" // or "reset"
15
+ * }
16
+ *
17
+ * Requirements:
18
+ * - Prisma CLI must be included in deployment or Lambda layer
19
+ * - DATABASE_URL environment variable must be set
20
+ * - VPC configuration for Aurora access
21
+ *
22
+ * Reference: https://www.prisma.io/docs/guides/deployment/deployment-guides/deploying-to-aws-lambda
23
+ */
24
+
25
+ const { execFile } = require('child_process');
26
+ const path = require('path');
27
+
28
+ /**
29
+ * Execute Prisma migration command
30
+ *
31
+ * @param {string} command - Migration command ('deploy' or 'reset')
32
+ * @param {string} schemaPath - Path to Prisma schema file
33
+ * @returns {Promise<number>} Exit code
34
+ */
35
+ async function executePrismaMigration(command, schemaPath) {
36
+ console.log(`Executing Prisma migration: ${command}`);
37
+ console.log(`Schema path: ${schemaPath}`);
38
+ console.log(`Database URL: ${process.env.DATABASE_URL ? '[SET]' : '[NOT SET]'}`);
39
+
40
+ return new Promise((resolve, reject) => {
41
+ // Build command arguments
42
+ const args = ['migrate', command];
43
+
44
+ // Add command-specific options
45
+ if (command === 'reset') {
46
+ args.push('--force'); // Skip confirmation prompt
47
+ args.push('--skip-generate'); // Skip client generation (already done in layer)
48
+ }
49
+
50
+ // Add schema path if provided
51
+ if (schemaPath) {
52
+ args.push('--schema', schemaPath);
53
+ }
54
+
55
+ console.log(`Running: prisma ${args.join(' ')}`);
56
+
57
+ // Execute Prisma CLI
58
+ execFile(
59
+ path.resolve('./node_modules/prisma/build/index.js'),
60
+ args,
61
+ {
62
+ env: {
63
+ ...process.env,
64
+ // Ensure Prisma uses the correct binary target
65
+ PRISMA_CLI_BINARY_TARGETS: 'rhel-openssl-3.0.x',
66
+ }
67
+ },
68
+ (error, stdout, stderr) => {
69
+ // Log all output
70
+ if (stdout) {
71
+ console.log('STDOUT:', stdout);
72
+ }
73
+ if (stderr) {
74
+ console.error('STDERR:', stderr);
75
+ }
76
+
77
+ if (error) {
78
+ console.error(`Migration ${command} exited with error:`, error.message);
79
+ console.error(`Exit code: ${error.code || 1}`);
80
+ resolve(error.code || 1);
81
+ } else {
82
+ console.log(`Migration ${command} completed successfully`);
83
+ resolve(0);
84
+ }
85
+ }
86
+ );
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Validate migration command
92
+ */
93
+ function validateCommand(command) {
94
+ const validCommands = ['deploy', 'reset'];
95
+
96
+ if (!validCommands.includes(command)) {
97
+ throw new Error(
98
+ `Invalid migration command: "${command}". ` +
99
+ `Valid commands are: ${validCommands.join(', ')}`
100
+ );
101
+ }
102
+
103
+ // Extra validation for dangerous commands
104
+ if (command === 'reset') {
105
+ const stage = process.env.STAGE || process.env.NODE_ENV;
106
+ if (stage === 'production' || stage === 'prod') {
107
+ throw new Error(
108
+ 'BLOCKED: "reset" command is not allowed in production environment. ' +
109
+ 'This command would delete all data. Use "deploy" instead.'
110
+ );
111
+ }
112
+ console.warn('⚠️ WARNING: "reset" will DELETE all data and reset the database!');
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Determine which Prisma schema to use based on database type
118
+ */
119
+ function getSchemaPath() {
120
+ // In Lambda, schemas are in @friggframework/core/generated/
121
+ const baseSchemaPath = './node_modules/@friggframework/core/generated';
122
+
123
+ // Check if Postgres is enabled
124
+ if (process.env.DATABASE_URL?.includes('postgresql') || process.env.DATABASE_URL?.includes('postgres')) {
125
+ const schemaPath = `${baseSchemaPath}/prisma-postgresql/schema.prisma`;
126
+ console.log(`Using PostgreSQL schema: ${schemaPath}`);
127
+ return schemaPath;
128
+ }
129
+
130
+ // Check if MongoDB is enabled
131
+ if (process.env.DATABASE_URL?.includes('mongodb')) {
132
+ const schemaPath = `${baseSchemaPath}/prisma-mongodb/schema.prisma`;
133
+ console.log(`Using MongoDB schema: ${schemaPath}`);
134
+ return schemaPath;
135
+ }
136
+
137
+ // Default to PostgreSQL
138
+ console.log('DATABASE_URL not set or database type unknown, defaulting to PostgreSQL');
139
+ return `${baseSchemaPath}/prisma-postgresql/schema.prisma`;
140
+ }
141
+
142
+ /**
143
+ * Lambda handler for database migrations
144
+ *
145
+ * @param {Object} event - Lambda event
146
+ * @param {string} event.command - Migration command ('deploy' or 'reset')
147
+ * @param {Object} context - Lambda context
148
+ * @returns {Promise<Object>} Migration result
149
+ */
150
+ exports.handler = async (event, context) => {
151
+ const startTime = Date.now();
152
+
153
+ console.log('='.repeat(60));
154
+ console.log('Database Migration Handler');
155
+ console.log('='.repeat(60));
156
+ console.log('Event:', JSON.stringify(event, null, 2));
157
+ console.log('Context:', JSON.stringify({
158
+ functionName: context.functionName,
159
+ functionVersion: context.functionVersion,
160
+ memoryLimitInMB: context.memoryLimitInMB,
161
+ logGroupName: context.logGroupName,
162
+ }, null, 2));
163
+
164
+ try {
165
+ // Get migration command (default to 'deploy')
166
+ const command = event.command || 'deploy';
167
+
168
+ // Validate command
169
+ validateCommand(command);
170
+
171
+ // Check required environment variables
172
+ if (!process.env.DATABASE_URL) {
173
+ throw new Error(
174
+ 'DATABASE_URL environment variable is not set. ' +
175
+ 'Cannot connect to database for migrations.'
176
+ );
177
+ }
178
+
179
+ // Determine schema path
180
+ const schemaPath = getSchemaPath();
181
+
182
+ // Execute migration
183
+ const exitCode = await executePrismaMigration(command, schemaPath);
184
+
185
+ const duration = Date.now() - startTime;
186
+
187
+ if (exitCode === 0) {
188
+ const result = {
189
+ success: true,
190
+ command,
191
+ message: `Migration ${command} completed successfully`,
192
+ duration: `${duration}ms`,
193
+ timestamp: new Date().toISOString(),
194
+ };
195
+
196
+ console.log('='.repeat(60));
197
+ console.log('Migration completed successfully');
198
+ console.log(JSON.stringify(result, null, 2));
199
+ console.log('='.repeat(60));
200
+
201
+ return result;
202
+ } else {
203
+ throw new Error(`Migration ${command} failed with exit code ${exitCode}`);
204
+ }
205
+
206
+ } catch (error) {
207
+ const duration = Date.now() - startTime;
208
+
209
+ console.error('='.repeat(60));
210
+ console.error('Migration failed');
211
+ console.error('Error:', error.message);
212
+ console.error('Stack:', error.stack);
213
+ console.error('='.repeat(60));
214
+
215
+ const errorResult = {
216
+ success: false,
217
+ command: event.command || 'unknown',
218
+ error: error.message,
219
+ duration: `${duration}ms`,
220
+ timestamp: new Date().toISOString(),
221
+ };
222
+
223
+ // Return error (don't throw) so Lambda doesn't retry
224
+ return errorResult;
225
+ }
226
+ };
227
+
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Lightweight dispatcher that executes integration event handlers.
3
+ * @param {import('../integrations/integration-base')} integrationInstance Pre-instantiated integration.
4
+ */
5
+ class IntegrationEventDispatcher {
6
+ constructor(integrationInstance) {
7
+ if (!integrationInstance) {
8
+ throw new Error('Integration instance is required');
9
+ }
10
+ this.integrationInstance = integrationInstance;
11
+ }
12
+
13
+ async dispatchHttp({ event, req, res, next }) {
14
+ const instance = this.integrationInstance;
15
+
16
+ const handler = this.findEventHandler(instance, event);
17
+
18
+ if (!handler) {
19
+ const name =
20
+ instance.constructor?.Definition?.name || 'integration';
21
+ throw new Error(`Event ${event} not registered for ${name}`);
22
+ }
23
+
24
+ return await handler.call(instance, { req, res, next });
25
+ }
26
+
27
+ async dispatchJob({ event, data, context }) {
28
+ const instance = this.integrationInstance;
29
+
30
+ const handler = this.findEventHandler(instance, event);
31
+
32
+ if (!handler) {
33
+ const name =
34
+ instance.constructor?.Definition?.name || 'integration';
35
+ throw new Error(`Event ${event} not registered for ${name}`);
36
+ }
37
+
38
+ return await handler.call(instance, { data, context });
39
+ }
40
+
41
+ findEventHandler(integration, event) {
42
+ if (integration.events && integration.events[event]) {
43
+ return integration.events[event].handler;
44
+ }
45
+
46
+ if (integration.defaultEvents && integration.defaultEvents[event]) {
47
+ return integration.defaultEvents[event].handler;
48
+ }
49
+
50
+ return null;
51
+ }
52
+ }
53
+
54
+ module.exports = { IntegrationEventDispatcher };