@friggframework/core 2.0.0-next.12 → 2.0.0-next.120

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 (410) hide show
  1. package/.eslintrc.json +18 -1
  2. package/CLAUDE.md +830 -0
  3. package/README.md +996 -50
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
  9. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
  10. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  12. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  13. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  14. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  15. package/application/commands/README.md +451 -0
  16. package/application/commands/admin-script-commands.js +359 -0
  17. package/application/commands/credential-commands.js +262 -0
  18. package/application/commands/entity-commands.js +294 -0
  19. package/application/commands/integration-commands.js +419 -0
  20. package/application/commands/integration-mapping-commands.js +25 -0
  21. package/application/commands/process-commands.js +135 -0
  22. package/application/commands/report-commands.js +188 -0
  23. package/application/commands/scheduler-commands.js +263 -0
  24. package/application/commands/usage-commands.js +56 -0
  25. package/application/commands/user-commands.js +312 -0
  26. package/application/index.js +83 -0
  27. package/artifacts/repositories/artifact-repository-factory.js +19 -0
  28. package/artifacts/repositories/artifact-repository-interface.js +27 -0
  29. package/artifacts/repositories/artifact-repository-local.js +42 -0
  30. package/artifacts/repositories/artifact-repository-s3.js +61 -0
  31. package/assertions/index.js +0 -3
  32. package/core/CLAUDE.md +715 -0
  33. package/core/Worker.js +55 -24
  34. package/core/create-handler.js +121 -38
  35. package/core/index.js +8 -1
  36. package/core/invocation-scope.js +225 -0
  37. package/core/parameters-to-env.js +257 -0
  38. package/core/secrets-to-env.js +7 -6
  39. package/core/ssm-preload.mjs +34 -0
  40. package/credential/repositories/credential-active-type.js +32 -0
  41. package/credential/repositories/credential-repository-documentdb.js +355 -0
  42. package/credential/repositories/credential-repository-factory.js +54 -0
  43. package/credential/repositories/credential-repository-interface.js +113 -0
  44. package/credential/repositories/credential-repository-mongo.js +294 -0
  45. package/credential/repositories/credential-repository-postgres.js +312 -0
  46. package/credential/repositories/credential-repository.js +300 -0
  47. package/credential/use-cases/get-credential-for-user.js +25 -0
  48. package/credential/use-cases/update-authentication-status.js +15 -0
  49. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  50. package/database/adapters/lambda-invoker.js +97 -0
  51. package/database/config.js +147 -0
  52. package/database/documentdb-encryption-service.js +334 -0
  53. package/database/documentdb-utils.js +196 -0
  54. package/database/encryption/README.md +863 -0
  55. package/database/encryption/documentdb-encryption-service.md +3575 -0
  56. package/database/encryption/encryption-schema-registry.js +404 -0
  57. package/database/encryption/field-encryption-service.js +254 -0
  58. package/database/encryption/integration-mapping-encryption.js +97 -0
  59. package/database/encryption/logger.js +82 -0
  60. package/database/encryption/prisma-encryption-extension.js +241 -0
  61. package/database/index.js +21 -21
  62. package/database/prisma.js +182 -0
  63. package/database/repositories/health-check-repository-documentdb.js +138 -0
  64. package/database/repositories/health-check-repository-factory.js +48 -0
  65. package/database/repositories/health-check-repository-interface.js +82 -0
  66. package/database/repositories/health-check-repository-mongodb.js +89 -0
  67. package/database/repositories/health-check-repository-postgres.js +82 -0
  68. package/database/repositories/migration-status-repository-s3.js +137 -0
  69. package/database/use-cases/check-database-health-use-case.js +29 -0
  70. package/database/use-cases/check-database-state-use-case.js +81 -0
  71. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  72. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  73. package/database/use-cases/get-migration-status-use-case.js +93 -0
  74. package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
  75. package/database/use-cases/run-database-migration-use-case.js +139 -0
  76. package/database/use-cases/test-encryption-use-case.js +253 -0
  77. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  78. package/database/utils/mongodb-collection-utils.js +94 -0
  79. package/database/utils/mongodb-schema-init.js +108 -0
  80. package/database/utils/prisma-runner.js +491 -0
  81. package/database/utils/prisma-schema-parser.js +182 -0
  82. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  83. package/encrypt/Cryptor.js +34 -168
  84. package/encrypt/index.js +1 -2
  85. package/errors/client-safe-error.js +26 -0
  86. package/errors/fetch-error.js +59 -57
  87. package/errors/index.js +6 -0
  88. package/errors/mapping-already-exists-error.js +14 -0
  89. package/generated/prisma-mongodb/client.d.ts +1 -0
  90. package/generated/prisma-mongodb/client.js +5 -0
  91. package/generated/prisma-mongodb/default.d.ts +1 -0
  92. package/generated/prisma-mongodb/default.js +5 -0
  93. package/generated/prisma-mongodb/edge.d.ts +1 -0
  94. package/generated/prisma-mongodb/edge.js +386 -0
  95. package/generated/prisma-mongodb/index-browser.js +368 -0
  96. package/generated/prisma-mongodb/index.d.ts +27065 -0
  97. package/generated/prisma-mongodb/index.js +411 -0
  98. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  99. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  100. package/generated/prisma-mongodb/package.json +183 -0
  101. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  102. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  103. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  104. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  105. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  106. package/generated/prisma-mongodb/runtime/library.js +147 -0
  107. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  108. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  109. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  110. package/generated/prisma-mongodb/schema.prisma +455 -0
  111. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  112. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  113. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  114. package/generated/prisma-mongodb/wasm.js +393 -0
  115. package/generated/prisma-postgresql/client.d.ts +1 -0
  116. package/generated/prisma-postgresql/client.js +5 -0
  117. package/generated/prisma-postgresql/default.d.ts +1 -0
  118. package/generated/prisma-postgresql/default.js +5 -0
  119. package/generated/prisma-postgresql/edge.d.ts +1 -0
  120. package/generated/prisma-postgresql/edge.js +407 -0
  121. package/generated/prisma-postgresql/index-browser.js +389 -0
  122. package/generated/prisma-postgresql/index.d.ts +29904 -0
  123. package/generated/prisma-postgresql/index.js +432 -0
  124. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  125. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  126. package/generated/prisma-postgresql/package.json +183 -0
  127. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  128. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  129. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  130. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  131. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  132. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  133. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  134. package/generated/prisma-postgresql/runtime/library.js +147 -0
  135. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  136. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  137. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  138. package/generated/prisma-postgresql/schema.prisma +436 -0
  139. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  140. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  141. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  142. package/generated/prisma-postgresql/wasm.js +414 -0
  143. package/handlers/WEBHOOKS.md +682 -0
  144. package/handlers/app-definition-loader.js +103 -0
  145. package/handlers/app-handler-helpers.js +20 -6
  146. package/handlers/backend-utils.js +272 -46
  147. package/handlers/database-migration-handler.js +236 -0
  148. package/handlers/integration-event-dispatcher.js +68 -0
  149. package/handlers/middleware/admin-auth.js +73 -0
  150. package/handlers/routers/HEALTHCHECK.md +342 -0
  151. package/handlers/routers/auth.js +4 -15
  152. package/handlers/routers/db-migration.handler.js +29 -0
  153. package/handlers/routers/db-migration.js +329 -0
  154. package/handlers/routers/health.js +518 -0
  155. package/handlers/routers/integration-defined-routers.js +85 -10
  156. package/handlers/routers/integration-webhook-routers.js +87 -0
  157. package/handlers/routers/user.js +27 -5
  158. package/handlers/routers/websocket.js +31 -9
  159. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  160. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  161. package/handlers/workers/db-migration.js +438 -0
  162. package/handlers/workers/dlq-processor.js +89 -0
  163. package/handlers/workers/integration-defined-workers.js +13 -7
  164. package/index.js +123 -47
  165. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  166. package/infrastructure/scheduler/index.js +33 -0
  167. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  168. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  169. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  170. package/integrations/EXTENSIONS.md +240 -0
  171. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  172. package/integrations/extension.js +254 -0
  173. package/integrations/index.js +20 -10
  174. package/integrations/integration-base.js +777 -74
  175. package/integrations/integration-router.js +399 -180
  176. package/integrations/options.js +1 -1
  177. package/integrations/repositories/config-patch-shared.js +43 -0
  178. package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
  179. package/integrations/repositories/integration-mapping-query.js +193 -0
  180. package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
  181. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  182. package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
  183. package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
  184. package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
  185. package/integrations/repositories/integration-mapping-repository.js +185 -0
  186. package/integrations/repositories/integration-repository-documentdb.js +354 -0
  187. package/integrations/repositories/integration-repository-factory.js +51 -0
  188. package/integrations/repositories/integration-repository-interface.js +219 -0
  189. package/integrations/repositories/integration-repository-mongo.js +502 -0
  190. package/integrations/repositories/integration-repository-postgres.js +550 -0
  191. package/integrations/repositories/process-repository-documentdb.js +311 -0
  192. package/integrations/repositories/process-repository-factory.js +53 -0
  193. package/integrations/repositories/process-repository-interface.js +136 -0
  194. package/integrations/repositories/process-repository-mongo.js +262 -0
  195. package/integrations/repositories/process-repository-postgres.js +380 -0
  196. package/integrations/repositories/process-update-ops-shared.js +112 -0
  197. package/integrations/repositories/report-id.js +13 -0
  198. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  199. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  200. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  201. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  202. package/integrations/use-cases/create-integration.js +253 -0
  203. package/integrations/use-cases/create-process.js +130 -0
  204. package/integrations/use-cases/delete-integration-for-user.js +130 -0
  205. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  206. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  207. package/integrations/use-cases/get-integration-for-user.js +78 -0
  208. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  209. package/integrations/use-cases/get-integration-instance.js +83 -0
  210. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  211. package/integrations/use-cases/get-possible-integrations.js +27 -0
  212. package/integrations/use-cases/get-process.js +89 -0
  213. package/integrations/use-cases/index.js +19 -0
  214. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  215. package/integrations/use-cases/load-integration-context.js +71 -0
  216. package/integrations/use-cases/patch-integration-config.js +39 -0
  217. package/integrations/use-cases/process-errors.js +28 -0
  218. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  219. package/integrations/use-cases/update-integration-config.js +32 -0
  220. package/integrations/use-cases/update-integration-messages.js +44 -0
  221. package/integrations/use-cases/update-integration-status.js +32 -0
  222. package/integrations/use-cases/update-integration.js +92 -0
  223. package/integrations/use-cases/update-process-metrics.js +222 -0
  224. package/integrations/use-cases/update-process-state.js +163 -0
  225. package/integrations/utils/assert-entity-ids-input.js +42 -0
  226. package/integrations/utils/map-integration-dto.js +37 -0
  227. package/jest-global-setup-noop.js +3 -0
  228. package/jest-global-teardown-noop.js +3 -0
  229. package/jest.config.js +2 -0
  230. package/logs/.eslintrc.json +26 -1
  231. package/logs/__fixtures__/events.js +82 -0
  232. package/logs/__fixtures__/matchers.js +45 -0
  233. package/logs/__fixtures__/secrets.js +59 -0
  234. package/logs/__fixtures__/vectors.js +329 -0
  235. package/logs/__fixtures__/with-env.js +45 -0
  236. package/logs/context.js +109 -0
  237. package/logs/contract-keys.js +67 -0
  238. package/logs/debug-shims.js +46 -0
  239. package/logs/denied-keys.js +21 -0
  240. package/logs/index.js +14 -3
  241. package/logs/jest-logger-setup.js +25 -0
  242. package/logs/levels.js +71 -0
  243. package/logs/logger-runtime.js +137 -0
  244. package/logs/logger.js +134 -46
  245. package/logs/record.js +276 -0
  246. package/logs/redact.js +328 -0
  247. package/logs/serialize.js +329 -0
  248. package/logs/sinks.js +93 -0
  249. package/logs/summarize-event.js +163 -0
  250. package/{module-plugin → modules}/index.js +0 -10
  251. package/modules/module-factory.js +56 -0
  252. package/modules/module.js +320 -0
  253. package/modules/repositories/module-repository-documentdb.js +350 -0
  254. package/modules/repositories/module-repository-factory.js +40 -0
  255. package/modules/repositories/module-repository-interface.js +145 -0
  256. package/modules/repositories/module-repository-mongo.js +436 -0
  257. package/modules/repositories/module-repository-postgres.js +481 -0
  258. package/modules/repositories/module-repository.js +369 -0
  259. package/modules/requester/api-key.js +52 -0
  260. package/modules/requester/oauth-2.js +561 -0
  261. package/modules/requester/requester.js +605 -0
  262. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  263. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  264. package/modules/tests/doubles/test-module-factory.js +16 -0
  265. package/modules/tests/doubles/test-module-repository.js +39 -0
  266. package/modules/use-cases/get-entities-for-user.js +32 -0
  267. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  268. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  269. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  270. package/modules/use-cases/get-module.js +74 -0
  271. package/modules/use-cases/process-authorization-callback.js +294 -0
  272. package/modules/use-cases/refresh-entity-options.js +72 -0
  273. package/modules/use-cases/test-module-auth.js +72 -0
  274. package/modules/utils/map-module-dto.js +20 -0
  275. package/modules/utils/to-credential-id.js +14 -0
  276. package/package.json +91 -52
  277. package/prisma-mongodb/schema.prisma +455 -0
  278. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  279. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  280. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  281. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  282. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  283. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  284. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  285. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  286. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  287. package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
  288. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  289. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  290. package/prisma-postgresql/schema.prisma +436 -0
  291. package/queues/queue-delivery.js +41 -0
  292. package/queues/queuer-util.js +89 -21
  293. package/reporting/README.md +154 -0
  294. package/reporting/builtin-reports.js +6 -0
  295. package/reporting/index.js +13 -0
  296. package/reporting/report-base.js +49 -0
  297. package/reporting/reports/integrations-report.js +221 -0
  298. package/syncs/manager.js +468 -443
  299. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  300. package/syncs/repositories/sync-repository-factory.js +43 -0
  301. package/syncs/repositories/sync-repository-interface.js +109 -0
  302. package/syncs/repositories/sync-repository-mongo.js +239 -0
  303. package/syncs/repositories/sync-repository-postgres.js +319 -0
  304. package/syncs/sync.js +0 -1
  305. package/telemetry/README.md +331 -0
  306. package/telemetry/bind-telemetry-context.js +77 -0
  307. package/telemetry/canonical-counters.js +52 -0
  308. package/telemetry/exporters.js +85 -0
  309. package/telemetry/index.js +26 -0
  310. package/telemetry/instrument-handler.js +104 -0
  311. package/telemetry/no-op-telemetry.js +71 -0
  312. package/telemetry/north-star.js +103 -0
  313. package/telemetry/otel-telemetry.js +232 -0
  314. package/telemetry/plugin-subscribers.js +77 -0
  315. package/telemetry/telemetry-config.js +120 -0
  316. package/telemetry/telemetry-context.js +51 -0
  317. package/telemetry/telemetry-event-bus.js +58 -0
  318. package/telemetry/telemetry-runtime.js +164 -0
  319. package/telemetry/telemetry-service.js +52 -0
  320. package/telemetry/usage-rollup-subscriber.js +116 -0
  321. package/token/repositories/token-repository-documentdb.js +137 -0
  322. package/token/repositories/token-repository-factory.js +40 -0
  323. package/token/repositories/token-repository-interface.js +131 -0
  324. package/token/repositories/token-repository-mongo.js +219 -0
  325. package/token/repositories/token-repository-postgres.js +264 -0
  326. package/token/repositories/token-repository.js +219 -0
  327. package/types/associations/index.d.ts +0 -17
  328. package/types/core/index.d.ts +12 -4
  329. package/types/database/index.d.ts +10 -2
  330. package/types/encrypt/index.d.ts +5 -3
  331. package/types/errors/index.d.ts +24 -10
  332. package/types/integrations/index.d.ts +3 -8
  333. package/types/logs/index.d.ts +61 -2
  334. package/types/module-plugin/index.d.ts +20 -69
  335. package/types/syncs/index.d.ts +0 -17
  336. package/usage/README.md +54 -0
  337. package/usage/index.js +17 -0
  338. package/usage/repositories/usage-repository-documentdb.js +194 -0
  339. package/usage/repositories/usage-repository-factory.js +25 -0
  340. package/usage/repositories/usage-repository-interface.js +37 -0
  341. package/usage/repositories/usage-repository-prisma.js +146 -0
  342. package/usage/tracked-metrics.js +38 -0
  343. package/usage/usage-windows.js +24 -0
  344. package/user/repositories/user-repository-documentdb.js +426 -0
  345. package/user/repositories/user-repository-factory.js +52 -0
  346. package/user/repositories/user-repository-interface.js +214 -0
  347. package/user/repositories/user-repository-mongo.js +323 -0
  348. package/user/repositories/user-repository-postgres.js +377 -0
  349. package/user/tests/doubles/test-user-repository.js +72 -0
  350. package/user/use-cases/authenticate-user.js +143 -0
  351. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  352. package/user/use-cases/create-individual-user.js +61 -0
  353. package/user/use-cases/create-organization-user.js +47 -0
  354. package/user/use-cases/create-token-for-user-id.js +30 -0
  355. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  356. package/user/use-cases/get-user-from-bearer-token.js +82 -0
  357. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  358. package/user/use-cases/login-user.js +122 -0
  359. package/user/user.js +125 -0
  360. package/utils/backend-path.js +38 -0
  361. package/utils/index.js +6 -0
  362. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  363. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  364. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  365. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  366. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  367. package/websocket/repositories/websocket-connection-repository.js +161 -0
  368. package/assertions/is-equal.js +0 -17
  369. package/associations/model.js +0 -54
  370. package/database/models/IndividualUser.js +0 -76
  371. package/database/models/OrganizationUser.js +0 -29
  372. package/database/models/State.js +0 -9
  373. package/database/models/Token.js +0 -70
  374. package/database/models/UserModel.js +0 -7
  375. package/database/models/WebsocketConnection.js +0 -49
  376. package/database/mongo.js +0 -45
  377. package/database/mongoose.js +0 -5
  378. package/encrypt/Cryptor.test.js +0 -32
  379. package/encrypt/encrypt.js +0 -132
  380. package/encrypt/encrypt.test.js +0 -1069
  381. package/encrypt/test-encrypt.js +0 -107
  382. package/errors/base-error.test.js +0 -32
  383. package/errors/fetch-error.test.js +0 -79
  384. package/errors/halt-error.test.js +0 -11
  385. package/errors/validation-errors.test.js +0 -120
  386. package/handlers/routers/middleware/loadUser.js +0 -15
  387. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  388. package/integrations/create-frigg-backend.js +0 -31
  389. package/integrations/integration-factory.js +0 -251
  390. package/integrations/integration-mapping.js +0 -43
  391. package/integrations/integration-model.js +0 -46
  392. package/integrations/integration-user.js +0 -144
  393. package/integrations/test/integration-base.test.js +0 -144
  394. package/lambda/TimeoutCatcher.test.js +0 -68
  395. package/logs/logger.test.js +0 -76
  396. package/module-plugin/auther.js +0 -393
  397. package/module-plugin/credential.js +0 -22
  398. package/module-plugin/entity-manager.js +0 -70
  399. package/module-plugin/entity.js +0 -46
  400. package/module-plugin/manager.js +0 -169
  401. package/module-plugin/module-factory.js +0 -61
  402. package/module-plugin/requester/api-key.js +0 -36
  403. package/module-plugin/requester/oauth-2.js +0 -219
  404. package/module-plugin/requester/requester.js +0 -165
  405. package/module-plugin/requester/requester.test.js +0 -28
  406. package/module-plugin/test/auther.test.js +0 -97
  407. package/syncs/model.js +0 -62
  408. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  409. /package/{module-plugin → modules}/requester/basic.js +0 -0
  410. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
package/core/CLAUDE.md ADDED
@@ -0,0 +1,715 @@
1
+ # CLAUDE.md - Frigg Core Runtime System
2
+
3
+ This file provides guidance to Claude Code when working with the Frigg Framework's core runtime system in `packages/core/core/`.
4
+
5
+ ## Critical Context (Read First)
6
+
7
+ - **Package Purpose**: Core runtime system and foundational classes for Frigg Lambda execution
8
+ - **Main Components**: Handler factory, Worker base class, Delegate pattern, Module loading
9
+ - **Core Architecture**: Lambda-optimized runtime with connection pooling, error handling, secrets management
10
+ - **Key Integration**: AWS Lambda, SQS job processing, MongoDB connections, AWS Secrets Manager
11
+ - **Security Model**: Automatic secrets injection, database connection management, user-facing error sanitization
12
+ - **DO NOT**: Expose internal errors to users, bypass connection pooling, skip database initialization
13
+
14
+ ## Core Components Architecture
15
+
16
+ ### Handler Creation System (`create-handler.js:9-67`)
17
+
18
+ **Purpose**: Factory for creating Lambda handlers with consistent infrastructure setup
19
+
20
+ **Key Features**:
21
+ - **Database Connection Management**: Automatic MongoDB connection with pooling
22
+ - **Secrets Management**: AWS Secrets Manager integration via `SECRET_ARN` env var
23
+ - **Error Sanitization**: Prevents internal details from leaking to end users
24
+ - **Invocation Scope**: `runInvocationScope` puts `requestId`, `route` and the redacted event summary on every record, then flushes usage, telemetry and sinks
25
+ - **Connection Optimization**: `context.callbackWaitsForEmptyEventLoop = false` for reuse
26
+
27
+ **Handler Configuration Options**:
28
+ ```javascript
29
+ const handler = createHandler({
30
+ eventName: 'MyIntegration', // Logged as handlerName
31
+ isUserFacingResponse: true, // true = sanitize errors, false = pass through
32
+ method: async (event, context) => {}, // Your Lambda function logic
33
+ shouldUseDatabase: true // false = skip MongoDB connection
34
+ });
35
+ ```
36
+
37
+ **Error Handling Patterns**:
38
+ - **User-Facing**: Returns 500 with generic "Internal Error Occurred" message
39
+ - **Server-to-Server**: Re-throws errors for AWS to handle
40
+ - **Halt Errors**: `error.isHaltError = true` logs but returns success (no retry)
41
+
42
+ ### Worker Base Class (`Worker.js:9-83`)
43
+
44
+ **Purpose**: Base class for SQS job processing with standardized patterns
45
+
46
+ **Core Responsibilities**:
47
+ - **Queue Management**: Get SQS queue URLs and send messages
48
+ - **Batch Processing**: Process multiple SQS records in sequence
49
+ - **Message Validation**: Extensible parameter validation system
50
+ - **Error Handling**: Structured error handling for async job processing
51
+
52
+ **Usage Pattern**:
53
+ ```javascript
54
+ class MyWorker extends Worker {
55
+ async _run(params, context = {}, delivery) {
56
+ // Your job processing logic here
57
+ // params are already JSON.parsed from SQS message body
58
+ // delivery is { receiveCount, maxReceiveCount, isLastAttempt }
59
+ }
60
+
61
+ _validateParams(params) {
62
+ // Validate required parameters
63
+ this._verifyParamExists(params, 'requiredField');
64
+ }
65
+ }
66
+
67
+ // In your Lambda handler
68
+ const worker = new MyWorker();
69
+ await worker.run(event, context); // Process SQS Records
70
+ ```
71
+
72
+ **Message Sending**:
73
+ ```javascript
74
+ await worker.send({
75
+ QueueUrl: 'https://sqs.region.amazonaws.com/account/queue',
76
+ jobType: 'processAttachment',
77
+ integrationId: 'abc123',
78
+ // ... other job parameters
79
+ }, delaySeconds);
80
+ ```
81
+
82
+ ### Delegate Pattern System (`Delegate.js:3-27`)
83
+
84
+ **Purpose**: Observer/delegation pattern for decoupled component communication
85
+
86
+ **Core Concepts**:
87
+ - **Notification System**: Components notify delegates of events/state changes
88
+ - **Type Safety**: `delegateTypes` array defines valid notification strings
89
+ - **Bidirectional**: Supports both sending and receiving notifications
90
+ - **Null Safety**: Gracefully handles missing delegates
91
+
92
+ **Implementation Pattern**:
93
+ ```javascript
94
+ class MyIntegration extends Delegate {
95
+ constructor(params) {
96
+ super(params);
97
+ this.delegateTypes = ['processComplete', 'errorOccurred', 'statusUpdate'];
98
+ }
99
+
100
+ async processData(data) {
101
+ // Do work
102
+ await this.notify('statusUpdate', { progress: 50 });
103
+ // More work
104
+ await this.notify('processComplete', { result: data });
105
+ }
106
+
107
+ async receiveNotification(notifier, delegateString, object) {
108
+ // Handle notifications from other components
109
+ switch(delegateString) {
110
+ case 'dataReady':
111
+ await this.processData(object);
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Module Loading System (`load-installed-modules.js:1-1085`)
119
+
120
+ **Purpose**: Dynamic loading and registration of integration modules
121
+
122
+ **Key Features**:
123
+ - **Package Discovery**: Automatically find `@friggframework/api-module-*` packages
124
+ - **Module Registration**: Load and register integration classes
125
+ - **Configuration Management**: Handle module-specific configuration
126
+ - **Dependency Resolution**: Manage inter-module dependencies
127
+
128
+ ## Runtime Lifecycle & Patterns
129
+
130
+ ### Lambda Handler Lifecycle
131
+ 1. **Pre-Execution Setup**:
132
+ ```javascript
133
+ runInvocationScope({ requestId, handlerName, method, route, invocation }, …); // Logger scope for the invocation
134
+ log.info('Handler invoked', { eventName: 'frigg.handler.invoked' });
135
+ await secretsToEnv(); // Secrets Manager injection
136
+ await parametersToEnv(); // SSM Parameter Store fetch (only when SSM_PARAMETER_PREFIX + FRIGG_SSM_OFFLOADED_KEYS are set)
137
+ context.callbackWaitsForEmptyEventLoop = false; // Connection pooling
138
+ ```
139
+
140
+ 2. **Database Connection**:
141
+ ```javascript
142
+ if (shouldUseDatabase) {
143
+ await connectToDatabase(); // MongoDB connection with pooling
144
+ }
145
+ ```
146
+
147
+ 3. **Method Execution**:
148
+ ```javascript
149
+ return await method(event, context); // Your integration logic
150
+ ```
151
+
152
+ 4. **Error Handling & Cleanup**:
153
+ ```javascript
154
+ // One record at the boundary: WARN frigg.handler.rejected (client-safe),
155
+ // ERROR frigg.handler.failed, or ERROR frigg.handler.halted (halt, no retry).
156
+ // Sanitized error response for user-facing endpoints; server-to-server
157
+ // errors are rethrown as a sanitized surrogate (toSanitizedSurrogate).
158
+ ```
159
+
160
+ 5. **Flush** (in `runInvocationScope`'s `finally`):
161
+ ```javascript
162
+ // Telemetry + log sinks under one deadline, fixed at flush start:
163
+ // min(flushTimeoutMs, remaining time - 50 ms). The usage rollup runs in
164
+ // parallel with no bound of its own.
165
+ ```
166
+
167
+ ### SQS Job Processing Lifecycle
168
+ 1. **Batch Processing**: Process all records in `event.Records` sequentially, each inside `runMessageScope` (adds `messageId`, `receiveCount`, `processId`, `integrationId`, `integrationEvent` to its records)
169
+ 2. **Message Parsing**: JSON.parse message body for parameters
170
+ 3. **Validation**: Run custom validation on parsed parameters
171
+ 4. **Execution**: Call `_run()` method with validated parameters
172
+ 5. **Error Propagation**: Let AWS handle retries/DLQ for failed jobs
173
+
174
+ ### Secrets Management Integration
175
+ - **Automatic Injection**: If `SECRET_ARN` environment variable is set
176
+ - **Environment Variables**: Secrets automatically set as `process.env` variables
177
+ - **Security**: No secrets logging or exposure in error messages
178
+ - **Caching**: Secrets cached for Lambda container lifetime
179
+
180
+ ### SSM Parameter Store Loader (`parameters-to-env.js`)
181
+ Offloads env vars that would otherwise exceed Lambda's 4KB env limit. Runs right after `secretsToEnv()` and is a no-op unless both `SSM_PARAMETER_PREFIX` and `FRIGG_SSM_OFFLOADED_KEYS` (comma-separated env var names) are set.
182
+
183
+ - **Precedence**: real `process.env` > Secrets Manager > SSM. A key already present in `process.env` at load time is never fetched or overwritten (a documented local-debugging escape hatch); only keys the loader itself set are refreshed.
184
+ - **Fetch**: `GetParametersCommand` with `WithDecryption: true`, batched in groups of 10 (the GetParameters max). Parameter name for key `K` is `${SSM_PARAMETER_PREFIX}/${K}`.
185
+ - **TTL cache**: successful loads cache for `FRIGG_SSM_CACHE_TTL` seconds (default 300; `0` = forever). Concurrent callers share one in-flight promise. A failed initial load is never cached, so the next invocation retries; a failed TTL *refresh* keeps serving the stale values (warn + 30s backoff) instead of erroring a warm container. `ThrottlingException` is retried with short exponential backoff.
186
+ - **Fail-fast**: throws listing every missing parameter name (and the prefix) if a required key is absent from SSM. Keys already satisfied by real `process.env` never trigger a failure.
187
+ - **Security**: logs parameter names and versions only, never values.
188
+
189
+ ## Database Connection Patterns
190
+
191
+ ### Connection Pooling Strategy
192
+ ```javascript
193
+ // Mongoose connection reuse across Lambda invocations
194
+ context.callbackWaitsForEmptyEventLoop = false;
195
+ await connectToDatabase(); // Reuses existing connection if available
196
+ ```
197
+
198
+ ### Database Usage Patterns
199
+ ```javascript
200
+ // Conditional database connection
201
+ const handler = createHandler({
202
+ shouldUseDatabase: false, // Skip for database-free operations
203
+ method: async (event) => {
204
+ // No DB operations needed
205
+ return { statusCode: 200, body: 'OK' };
206
+ }
207
+ });
208
+ ```
209
+
210
+ ## Error Handling Architecture
211
+
212
+ ### Error Classification
213
+ 1. **User-Facing Errors**: `isUserFacingResponse: true`
214
+ - Returns generic 500 error message
215
+ - Prevents information disclosure
216
+ - Logs full error details internally
217
+
218
+ 2. **Server-to-Server Errors**: `isUserFacingResponse: false`
219
+ - Logs one ERROR, then rethrows a sanitized surrogate for AWS handling:
220
+ a fresh `Error` with the sanitized `name`, `message`, `stack`, plus
221
+ `statusCode` and `code`. `instanceof` checks and custom properties are gone
222
+ - Used for SQS, SNS, and internal API calls
223
+ - Enables proper retry mechanisms
224
+
225
+ 3. **Halt Errors**: `error.isHaltError = true`
226
+ - Logs error but returns success
227
+ - Prevents infinite retries for known issues
228
+ - Used for graceful degradation scenarios
229
+
230
+ ### Logging Strategy
231
+ ```javascript
232
+ const { getLogger } = require('../logs');
233
+ const log = getLogger('frigg.core.sync');
234
+ log.info('Sync started', { eventName: 'frigg.core.sync.started', processId });
235
+ // Throw with cause; the boundary logs the error one time.
236
+ ```
237
+ See `docs/guides/LOGGING.md`.
238
+
239
+ ## Integration Development Patterns
240
+
241
+ ### Extending Worker for Job Processing
242
+ ```javascript
243
+ class AttachmentWorker extends Worker {
244
+ _validateParams(params) {
245
+ this._verifyParamExists(params, 'integrationId');
246
+ this._verifyParamExists(params, 'attachmentUrl');
247
+ this._verifyParamExists(params, 'destination');
248
+ }
249
+
250
+ async _run(params, context) {
251
+ const { integrationId, attachmentUrl, destination } = params;
252
+ // Process attachment upload/download
253
+ // Handle errors gracefully
254
+ // Update job status
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Creating Custom Handlers
260
+ ```javascript
261
+ const myIntegrationHandler = createHandler({
262
+ eventName: 'MyIntegration',
263
+ isUserFacingResponse: true, // Sanitize errors for users
264
+ shouldUseDatabase: true, // Need database access
265
+ method: async (event, context) => {
266
+ // Your integration logic here
267
+ // Database is already connected
268
+ // Secrets are in process.env
269
+
270
+ return {
271
+ statusCode: 200,
272
+ body: JSON.stringify({ success: true })
273
+ };
274
+ }
275
+ });
276
+ ```
277
+
278
+ ### Delegate Pattern for Integration Communication
279
+ ```javascript
280
+ class IntegrationManager extends Delegate {
281
+ constructor() {
282
+ super();
283
+ this.delegateTypes = [
284
+ 'authenticationComplete',
285
+ 'syncStarted',
286
+ 'syncComplete',
287
+ 'errorOccurred'
288
+ ];
289
+ }
290
+
291
+ async startSync(integrationId) {
292
+ await this.notify('syncStarted', { integrationId });
293
+ // ... sync logic ...
294
+ await this.notify('syncComplete', { integrationId, recordCount: 100 });
295
+ }
296
+ }
297
+ ```
298
+
299
+ ## Performance Optimization Patterns
300
+
301
+ ### Connection Reuse
302
+ ```javascript
303
+ // ALWAYS set this in handlers for performance
304
+ context.callbackWaitsForEmptyEventLoop = false;
305
+ ```
306
+
307
+ ### Conditional Database Usage
308
+ ```javascript
309
+ // Skip database for lightweight operations
310
+ const handler = createHandler({
311
+ shouldUseDatabase: false, // Faster cold starts
312
+ method: healthCheckMethod
313
+ });
314
+ ```
315
+
316
+ ### SQS Batch Processing Optimization
317
+ ```javascript
318
+ // Process records sequentially (not parallel) for resource control
319
+ for (const record of records) {
320
+ await this._run(JSON.parse(record.body), context);
321
+ }
322
+ ```
323
+
324
+ ## Repository & Use Case Architecture
325
+
326
+ The Frigg Framework follows DDD/Hexagonal Architecture with clear separation between handlers, use cases, and repositories.
327
+
328
+ ### Repository Pattern in Core
329
+
330
+ **Purpose**: Abstract database and external system access into dedicated repository classes.
331
+
332
+ **Structure**:
333
+ ```javascript
334
+ // Example: packages/core/database/websocket-connection-repository.js
335
+ class WebsocketConnectionRepository {
336
+ /**
337
+ * Create a new WebSocket connection record
338
+ * Pure database operation - no business logic
339
+ */
340
+ async createConnection(connectionId) {
341
+ return await WebsocketConnection.create({ connectionId });
342
+ }
343
+
344
+ /**
345
+ * Delete a WebSocket connection record
346
+ * Returns raw deletion result
347
+ */
348
+ async deleteConnection(connectionId) {
349
+ return await WebsocketConnection.deleteOne({ connectionId });
350
+ }
351
+
352
+ /**
353
+ * Get all active connections
354
+ * Returns raw data from database
355
+ */
356
+ async getActiveConnections() {
357
+ return await WebsocketConnection.getActiveConnections();
358
+ }
359
+ }
360
+ ```
361
+
362
+ **Repository Responsibilities**:
363
+ - ✅ **CRUD operations** - Create, Read, Update, Delete database records
364
+ - ✅ **Query execution** - Run database queries and return results
365
+ - ✅ **Data access only** - No interpretation or decision-making
366
+ - ✅ **Atomic operations** - Each method performs one database operation
367
+ - ❌ **NO business logic** - Don't decide what data means or what to do with it
368
+ - ❌ **NO orchestration** - Don't coordinate multiple operations
369
+
370
+ **Real Repository Examples**:
371
+ - `WebsocketConnectionRepository` - WebSocket persistence (packages/core/database/websocket-connection-repository.js)
372
+ - `SyncRepository` - Sync object management (packages/core/syncs/sync-repository.js)
373
+ - `IntegrationMappingRepository` - Integration mappings (packages/core/integrations/integration-mapping-repository.js)
374
+ - `TokenRepository` - Token operations (packages/core/database/token-repository.js)
375
+ - `HealthCheckRepository` - Health check data access (packages/core/database/health-check-repository.js)
376
+
377
+ ### Use Case Pattern in Core
378
+
379
+ **Purpose**: Contain business logic, orchestration, and workflow coordination.
380
+
381
+ **Structure**:
382
+ ```javascript
383
+ // Example: packages/core/database/use-cases/check-database-health-use-case.js
384
+ class CheckDatabaseHealthUseCase {
385
+ constructor({ healthCheckRepository }) {
386
+ // Dependency injection - receive repository via constructor
387
+ this.repository = healthCheckRepository;
388
+ }
389
+
390
+ async execute() {
391
+ // 1. Get raw data from repository
392
+ const { stateName, isConnected } = this.repository.getDatabaseConnectionState();
393
+
394
+ // 2. Apply business logic - determine health status
395
+ const result = {
396
+ status: isConnected ? 'healthy' : 'unhealthy',
397
+ state: stateName,
398
+ };
399
+
400
+ // 3. Orchestration - conditionally perform additional checks
401
+ if (isConnected) {
402
+ result.responseTime = await this.repository.pingDatabase(2000);
403
+ }
404
+
405
+ return result;
406
+ }
407
+ }
408
+ ```
409
+
410
+ **Use Case Responsibilities**:
411
+ - ✅ **Business logic** - Make decisions based on data
412
+ - ✅ **Orchestration** - Coordinate multiple repository calls
413
+ - ✅ **Validation** - Enforce business rules
414
+ - ✅ **Workflow** - Determine what happens next
415
+ - ✅ **Error handling** - Handle domain-specific errors
416
+ - ❌ **NO direct database access** - Always use repositories
417
+ - ❌ **NO HTTP concerns** - Don't know about status codes or headers
418
+
419
+ **Real Use Case Examples**:
420
+ - `CheckDatabaseHealthUseCase` - Database health business logic (packages/core/database/use-cases/check-database-health-use-case.js)
421
+ - `TestEncryptionUseCase` - Encryption testing workflow (packages/core/database/use-cases/test-encryption-use-case.js)
422
+
423
+ ### Handler Pattern in Core
424
+
425
+ **Purpose**: Translate Lambda/HTTP/SQS events into use case calls.
426
+
427
+ **Handler Should ONLY**:
428
+ - Define routes and event handlers
429
+ - Call use cases (NOT repositories)
430
+ - Map use case results to HTTP/Lambda responses
431
+ - Handle protocol-specific concerns (status codes, headers)
432
+
433
+ **❌ WRONG - Handler contains business logic**:
434
+ ```javascript
435
+ // BAD: Business logic in handler
436
+ router.get('/health', async (req, res) => {
437
+ const state = mongoose.connection.readyState;
438
+ const isHealthy = state === 1; // ❌ Business logic in handler
439
+
440
+ if (isHealthy) { // ❌ Orchestration in handler
441
+ const pingStart = Date.now();
442
+ await mongoose.connection.db.admin().ping(); // ❌ Direct DB access
443
+ const responseTime = Date.now() - pingStart;
444
+ res.json({ status: 'healthy', responseTime });
445
+ }
446
+ });
447
+ ```
448
+
449
+ **✅ CORRECT - Handler delegates to use case**:
450
+ ```javascript
451
+ // GOOD: Handler calls use case
452
+ const healthCheckRepository = new HealthCheckRepository();
453
+ const checkDatabaseHealthUseCase = new CheckDatabaseHealthUseCase({
454
+ healthCheckRepository
455
+ });
456
+
457
+ router.get('/health', async (req, res) => {
458
+ // Call use case - all business logic is there
459
+ const health = await checkDatabaseHealthUseCase.execute();
460
+
461
+ // Handler only maps to HTTP response
462
+ const statusCode = health.status === 'healthy' ? 200 : 503;
463
+ res.status(statusCode).json(health);
464
+ });
465
+ ```
466
+
467
+ ### Dependency Direction
468
+
469
+ **The Golden Rule**:
470
+ > "Handlers ONLY call Use Cases, NEVER Repositories or Business Logic directly"
471
+
472
+ **Correct Flow**:
473
+ ```
474
+ Handler/Router (createHandler)
475
+ ↓ calls
476
+ Use Case (execute)
477
+ ↓ calls
478
+ Repository (CRUD methods)
479
+ ↓ accesses
480
+ Database/External System
481
+ ```
482
+
483
+ **Why This Matters**:
484
+ - **Testability**: Use cases can be tested with mocked repositories
485
+ - **Reusability**: Use cases can be called from handlers, CLI, background jobs
486
+ - **Maintainability**: Business logic is centralized, not scattered across handlers
487
+ - **Flexibility**: Swap repository implementations without changing use cases
488
+
489
+ ### Migration from Old Patterns
490
+
491
+ **Old Pattern (Mongoose models everywhere)**:
492
+ ```javascript
493
+ // BAD: Direct model access in handlers
494
+ const handler = createHandler({
495
+ method: async (event) => {
496
+ const user = await User.findById(event.userId); // ❌ Direct model access
497
+ if (!user.isActive) { // ❌ Business logic in handler
498
+ throw new Error('User not active');
499
+ }
500
+ await Sync.create({ userId: user.id }); // ❌ Direct model access
501
+ }
502
+ });
503
+ ```
504
+
505
+ **New Pattern (Repository + Use Case)**:
506
+ ```javascript
507
+ // GOOD: Repository abstracts data access
508
+ class UserRepository {
509
+ async findById(userId) {
510
+ return await User.findById(userId);
511
+ }
512
+ }
513
+
514
+ class SyncRepository {
515
+ async createSync(data) {
516
+ return await Sync.create(data);
517
+ }
518
+ }
519
+
520
+ // GOOD: Use case contains business logic
521
+ class ActivateUserSyncUseCase {
522
+ constructor({ userRepository, syncRepository }) {
523
+ this.userRepo = userRepository;
524
+ this.syncRepo = syncRepository;
525
+ }
526
+
527
+ async execute(userId) {
528
+ const user = await this.userRepo.findById(userId);
529
+
530
+ if (!user.isActive) { // ✅ Business logic in use case
531
+ throw new Error('User not active');
532
+ }
533
+
534
+ return await this.syncRepo.createSync({ userId: user.id });
535
+ }
536
+ }
537
+
538
+ // GOOD: Handler delegates to use case
539
+ const handler = createHandler({
540
+ method: async (event) => {
541
+ const useCase = new ActivateUserSyncUseCase({
542
+ userRepository: new UserRepository(),
543
+ syncRepository: new SyncRepository()
544
+ });
545
+ return await useCase.execute(event.userId);
546
+ }
547
+ });
548
+ ```
549
+
550
+ ### Integration with Worker Pattern
551
+
552
+ **Workers should also follow this pattern**:
553
+
554
+ ```javascript
555
+ class ProcessAttachmentWorker extends Worker {
556
+ constructor() {
557
+ super();
558
+ // Inject repositories into use case
559
+ this.useCase = new ProcessAttachmentUseCase({
560
+ asanaRepository: new AsanaRepository(),
561
+ frontifyRepository: new FrontifyRepository()
562
+ });
563
+ }
564
+
565
+ _validateParams(params) {
566
+ this._verifyParamExists(params, 'attachmentId');
567
+ }
568
+
569
+ async _run(params, context) {
570
+ // Worker delegates to use case
571
+ return await this.useCase.execute(params.attachmentId);
572
+ }
573
+ }
574
+ ```
575
+
576
+ ### When to Extract to Repository/Use Case
577
+
578
+ **Extract to Repository when you see**:
579
+ - Direct Mongoose model calls (`User.findById()`, `Sync.create()`)
580
+ - Database queries in handlers or business logic
581
+ - External API calls scattered across codebase
582
+ - File system or AWS SDK operations in handlers
583
+
584
+ **Extract to Use Case when you see**:
585
+ - Business logic in handlers (if/else based on data)
586
+ - Orchestration of multiple operations
587
+ - Validation and error handling logic
588
+ - Workflow coordination
589
+
590
+ ### Testing with Repository/Use Case Pattern
591
+
592
+ **Repository Tests** (Integration tests with real DB):
593
+ ```javascript
594
+ describe('WebsocketConnectionRepository', () => {
595
+ it('creates connection record', async () => {
596
+ const repo = new WebsocketConnectionRepository();
597
+ const result = await repo.createConnection('conn-123');
598
+ expect(result.connectionId).toBe('conn-123');
599
+ });
600
+ });
601
+ ```
602
+
603
+ **Use Case Tests** (Unit tests with mocked repositories):
604
+ ```javascript
605
+ describe('CheckDatabaseHealthUseCase', () => {
606
+ it('returns unhealthy when disconnected', async () => {
607
+ const mockRepo = {
608
+ getDatabaseConnectionState: () => ({
609
+ stateName: 'disconnected',
610
+ isConnected: false
611
+ })
612
+ };
613
+ const useCase = new CheckDatabaseHealthUseCase({
614
+ healthCheckRepository: mockRepo
615
+ });
616
+ const result = await useCase.execute();
617
+ expect(result.status).toBe('unhealthy');
618
+ });
619
+ });
620
+ ```
621
+
622
+ **Handler Tests** (HTTP/Lambda response tests):
623
+ ```javascript
624
+ describe('Health Handler', () => {
625
+ it('returns 503 when unhealthy', async () => {
626
+ // Mock use case
627
+ const mockUseCase = {
628
+ execute: async () => ({ status: 'unhealthy' })
629
+ };
630
+ // Test HTTP response
631
+ const response = await handler(mockEvent, mockContext);
632
+ expect(response.statusCode).toBe(503);
633
+ });
634
+ });
635
+ ```
636
+
637
+ ## Anti-Patterns to Avoid
638
+
639
+ ### Core Runtime Anti-Patterns
640
+ ❌ **Don't expose internal errors** to user-facing endpoints - use `isUserFacingResponse: true`
641
+ ❌ **Don't skip connection optimization** - always set `callbackWaitsForEmptyEventLoop = false`
642
+ ❌ **Don't parallel process SQS records** - sequential processing prevents resource exhaustion
643
+ ❌ **Don't hardcode queue URLs** - use the Worker's `getQueueURL()` method
644
+ ❌ **Don't bypass parameter validation** - always implement `_validateParams()` in Workers
645
+ ❌ **Don't leak secrets in logs** - the system handles this, don't override
646
+ ❌ **Don't ignore delegate types** - define valid `delegateTypes` array for type safety
647
+
648
+ ### DDD/Hexagonal Architecture Anti-Patterns
649
+ ❌ **Don't access models directly in handlers** - create repositories to abstract data access
650
+ ❌ **Don't put business logic in handlers** - extract to use cases
651
+ ❌ **Don't call repositories from handlers** - always go through use cases
652
+ ❌ **Don't put orchestration in repositories** - repositories should be atomic CRUD operations
653
+ ❌ **Don't skip dependency injection** - inject repositories into use cases via constructor
654
+ ❌ **Don't create "god" use cases** - keep use cases focused on single business operations
655
+ ❌ **Don't mix database queries with business logic** - separate into repository + use case
656
+
657
+ ## Testing Patterns
658
+
659
+ ### Handler Testing
660
+ ```javascript
661
+ const { createHandler } = require('@friggframework/core/core');
662
+
663
+ const testHandler = createHandler({
664
+ isUserFacingResponse: false, // Rethrows a sanitized surrogate (name, message, statusCode, code; not the original instance)
665
+ shouldUseDatabase: false, // Mock/skip DB in tests
666
+ method: yourTestMethod
667
+ });
668
+
669
+ // Test with mock event/context
670
+ const result = await testHandler(mockEvent, mockContext);
671
+ ```
672
+
673
+ ### Worker Testing
674
+ ```javascript
675
+ class TestWorker extends Worker {
676
+ _validateParams(params) {
677
+ this._verifyParamExists(params, 'testField');
678
+ }
679
+
680
+ async _run(params, context) {
681
+ // Your test logic
682
+ return { processed: true };
683
+ }
684
+ }
685
+
686
+ // Test SQS record processing
687
+ const worker = new TestWorker();
688
+ await worker.run({
689
+ Records: [{
690
+ body: JSON.stringify({ testField: 'value' })
691
+ }]
692
+ });
693
+ ```
694
+
695
+ ## Environment Variables
696
+
697
+ ### Required Variables
698
+ - `AWS_REGION`: AWS region for SQS operations
699
+ - `SECRET_ARN`: (Optional) AWS Secrets Manager secret ARN for automatic injection
700
+
701
+ ### Database Variables
702
+ - MongoDB connection variables (handled by `../database/mongo`)
703
+ - See database module documentation for complete list
704
+
705
+ ### Queue Variables
706
+ - Queue URLs typically passed as parameters, not environment variables
707
+ - Use Worker's `getQueueURL()` method for dynamic queue discovery
708
+
709
+ ## Security Considerations
710
+
711
+ - **Secrets**: Never log or expose secrets in error messages
712
+ - **Error Messages**: Always sanitize errors for user-facing responses
713
+ - **Database**: Connection pooling reuses connections securely
714
+ - **SQS**: Message validation prevents injection attacks
715
+ - **Logging**: Debug logs include sensitive data - handle carefully in production