@friggframework/core 2.0.0-next.10 → 2.0.0-next.101

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 +633 -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 +287 -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 +334 -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
@@ -0,0 +1,334 @@
1
+ const fetch = require('node-fetch');
2
+ const { Delegate } = require('../../core');
3
+ const { FetchError } = require('../../errors');
4
+ const { get } = require('../../assertions');
5
+
6
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
7
+ const MAX_AUTH_RETRIES = 3;
8
+
9
+ class Requester extends Delegate {
10
+ constructor(params) {
11
+ super(params);
12
+ this.backOff = get(params, 'backOff', [1, 3, 10, 30, 60, 180]);
13
+ this.isRefreshable = false;
14
+ this.refreshCount = 0;
15
+ this.authGraceRetryCount = 0;
16
+ this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
17
+ this.delegateTypes.push(this.DLGT_INVALID_AUTH);
18
+ this.agent = get(params, 'agent', null);
19
+
20
+ // Per-attempt HTTP timeout. Without this the framework called fetch()
21
+ // with no AbortController and no timeout — a silently-hung TCP
22
+ // connection (server accepts but never responds) blocked the calling
23
+ // promise forever, cascading into stalled batches, stalled syncs,
24
+ // and worker-lambda timeouts.
25
+ //
26
+ // Configuration precedence:
27
+ // 1. Instance param: new Requester({ requestTimeoutMs: 30_000 })
28
+ // 2. Class static: static requestTimeoutMs = 30_000
29
+ // 3. Default: DEFAULT_REQUEST_TIMEOUT_MS (60s)
30
+ //
31
+ // Pass 0 (or null) to disable the timeout entirely — reserved for
32
+ // test doubles and documented long-running endpoints.
33
+ // Intentionally NOT using `get(params, ...)` here — the Frigg
34
+ // `get` helper throws RequiredPropertyError if the key is missing
35
+ // and no default is provided, which would collide with the fall-
36
+ // through to the class-level static override.
37
+ const instanceTimeout = params?.requestTimeoutMs;
38
+ this.requestTimeoutMs =
39
+ instanceTimeout !== undefined && instanceTimeout !== null
40
+ ? instanceTimeout
41
+ : this.constructor.requestTimeoutMs ??
42
+ DEFAULT_REQUEST_TIMEOUT_MS;
43
+
44
+ // Allow passing in the fetch function
45
+ // Instance methods can use this.fetch without differentiating
46
+ this.fetch = get(params, 'fetch', fetch);
47
+ }
48
+
49
+ parsedBody = async (resp) => {
50
+ const contentType = resp.headers.get('Content-Type') || '';
51
+
52
+ if (
53
+ contentType.match(/^application\/json/) ||
54
+ contentType.match(/^application\/vnd.api\+json/) ||
55
+ contentType.match(/^application\/hal\+json/)
56
+ ) {
57
+ return resp.json();
58
+ }
59
+
60
+ return resp.text();
61
+ };
62
+
63
+ /**
64
+ * @param {string} url - The request URL, relative or absolute.
65
+ * @param {Object} options - Fetch options (method, headers, body, query,
66
+ * returnFullRes, etc.) built by the `_get`/`_post`/`_patch`/`_put`/
67
+ * `_delete` wrappers.
68
+ * @param {number} attempt - 0-based count of retries already made for
69
+ * this call. Indexes `this.backOff` for the next delay and is passed
70
+ * back in on each recursive retry.
71
+ */
72
+ async _request(url, options, attempt = 0) {
73
+ let encodedUrl = encodeURI(url);
74
+ if (options.query) {
75
+ let queryBuild = '?';
76
+ for (const key in options.query) {
77
+ queryBuild += `${encodeURIComponent(key)}=${encodeURIComponent(
78
+ options.query[key]
79
+ )}&`;
80
+ }
81
+ encodedUrl += queryBuild.slice(0, -1);
82
+ }
83
+
84
+ options.headers = await this.addAuthHeaders(options.headers);
85
+
86
+ if (this.agent) options.agent = this.agent;
87
+
88
+ // Per-attempt timeout — fresh AbortController per call so the retry
89
+ // recursion (with its own backoff sleeps) always gets a clean
90
+ // signal. Timer is cleared in the finally block regardless of
91
+ // outcome.
92
+ const timeoutMs = this.requestTimeoutMs;
93
+ const controller = timeoutMs > 0 ? new AbortController() : null;
94
+ const timeoutHandle = controller
95
+ ? setTimeout(() => controller.abort(), timeoutMs)
96
+ : null;
97
+ const fetchOptions = controller
98
+ ? { ...options, signal: controller.signal }
99
+ : options;
100
+
101
+ // Timer must stay active through body consumption. node-fetch v2
102
+ // resolves the fetch() promise when headers arrive, not when the
103
+ // body is fully read — so a server that sends headers and then
104
+ // stalls the body would still hang parsedBody() or
105
+ // FetchError.create()'s response.text() call. We clear the timer
106
+ // only after the body is fully consumed (success path) or
107
+ // deliberately before each recursive retry so the new attempt
108
+ // starts with its own fresh timer.
109
+ let timerCleared = false;
110
+ const clearRequestTimer = () => {
111
+ if (!timerCleared && timeoutHandle) {
112
+ clearTimeout(timeoutHandle);
113
+ timerCleared = true;
114
+ }
115
+ };
116
+
117
+ try {
118
+ let response;
119
+ try {
120
+ response = await this.fetch(encodedUrl, fetchOptions);
121
+ } catch (e) {
122
+ // AbortController fires AbortError (name) / ETIMEDOUT-shaped
123
+ // errors (type on node-fetch) when we hit the timeout. No
124
+ // retry on timeout: a slow endpoint is a downstream problem,
125
+ // and each retry would wait another `timeoutMs` before giving
126
+ // up — amplifying the hang into a per-record multi-minute
127
+ // stall at batch scale.
128
+ const isTimeout =
129
+ e?.name === 'AbortError' || e?.type === 'aborted';
130
+ if (e?.code === 'ECONNRESET' && attempt < this.backOff.length) {
131
+ clearRequestTimer();
132
+ const delay = this.backOff[attempt] * 1000;
133
+ await new Promise((resolve) => setTimeout(resolve, delay));
134
+ return this._request(url, options, attempt + 1);
135
+ }
136
+ const fetchError = await FetchError.create({
137
+ resource: encodedUrl,
138
+ init: options,
139
+ responseBody: isTimeout
140
+ ? `Request timed out after ${timeoutMs}ms`
141
+ : e,
142
+ });
143
+ if (isTimeout) {
144
+ // Flag + machine-readable fields so callers can
145
+ // distinguish a timeout from a generic network error
146
+ // without parsing the message (which FetchError
147
+ // sanitizes outside of STAGE=dev).
148
+ fetchError.isTimeout = true;
149
+ fetchError.timeoutMs = timeoutMs;
150
+ }
151
+ throw fetchError;
152
+ }
153
+
154
+ const { status } = response;
155
+
156
+ // If the status is retriable and there are back off requests left, retry the request
157
+ if (
158
+ (status === 429 || status >= 500) &&
159
+ attempt < this.backOff.length
160
+ ) {
161
+ clearRequestTimer();
162
+ const delay = this.backOff[attempt] * 1000;
163
+ await new Promise((resolve) => setTimeout(resolve, delay));
164
+ return this._request(url, options, attempt + 1);
165
+ }
166
+
167
+ if (status === 401) {
168
+ if (!this.isRefreshable) {
169
+ // Up to MAX_AUTH_RETRIES grace retries before invalidating
170
+ // — a 401 alone isn't proof the credential is bad.
171
+ if (
172
+ this.authGraceRetryCount < MAX_AUTH_RETRIES &&
173
+ this.authGraceRetryCount < this.backOff.length
174
+ ) {
175
+ const delay =
176
+ this.backOff[this.authGraceRetryCount] * 1000;
177
+ this.authGraceRetryCount++;
178
+ clearRequestTimer();
179
+ await new Promise((resolve) =>
180
+ setTimeout(resolve, delay)
181
+ );
182
+ return this._request(url, options, attempt + 1);
183
+ }
184
+
185
+ throw await this._invalidateAuth(
186
+ encodedUrl,
187
+ options,
188
+ response
189
+ );
190
+ }
191
+
192
+ if (this.refreshCount < MAX_AUTH_RETRIES) {
193
+ this.refreshCount++;
194
+ const refreshSucceeded = await this.refreshAuth();
195
+ if (refreshSucceeded) {
196
+ clearRequestTimer();
197
+ return this._request(url, options, attempt + 1);
198
+ }
199
+
200
+ throw await this._invalidateAuth(
201
+ encodedUrl,
202
+ options,
203
+ response
204
+ );
205
+ }
206
+
207
+ throw await this._invalidateAuth(encodedUrl, options, response);
208
+ }
209
+
210
+ // If the error wasn't retried, throw. FetchError.create reads
211
+ // the response body (response.text()) — timer must still be
212
+ // alive to catch a stalled body stream.
213
+ if (status >= 400) {
214
+ const fetchError = await FetchError.create({
215
+ resource: encodedUrl,
216
+ init: options,
217
+ response,
218
+ });
219
+ throw this._maybeFlagTimeoutDuringBodyRead(
220
+ fetchError,
221
+ timeoutMs
222
+ );
223
+ }
224
+
225
+ // Successful response: reset the per-instance refresh budget so
226
+ // a later 401 in the same Requester lifetime can attempt refresh
227
+ // again instead of silently falling through.
228
+ this.refreshCount = 0;
229
+ this.authGraceRetryCount = 0;
230
+
231
+ // parsedBody consumes the response body stream. If the server
232
+ // stalls mid-stream the timer (still armed) aborts it.
233
+ return options.returnFullRes
234
+ ? response
235
+ : await this.parsedBody(response);
236
+ } catch (e) {
237
+ // If the abort fired during body consumption, node-fetch emits
238
+ // the error as an AbortError on the body stream. Surface the
239
+ // same isTimeout flag callers use for header-phase timeouts.
240
+ throw this._maybeFlagTimeoutDuringBodyRead(e, timeoutMs);
241
+ } finally {
242
+ clearRequestTimer();
243
+ }
244
+ }
245
+
246
+ async _invalidateAuth(encodedUrl, options, response) {
247
+ const fetchError = await FetchError.create({
248
+ resource: encodedUrl,
249
+ init: options,
250
+ response,
251
+ });
252
+ await this.notify(this.DLGT_INVALID_AUTH, fetchError);
253
+ return fetchError;
254
+ }
255
+
256
+ _maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
257
+ if (!err || typeof err !== 'object') return err;
258
+ if (err.isTimeout) return err;
259
+ const isAbort = err.name === 'AbortError' || err.type === 'aborted';
260
+ if (!isAbort) return err;
261
+ err.isTimeout = true;
262
+ err.timeoutMs = timeoutMs;
263
+ return err;
264
+ }
265
+
266
+ async _get(options) {
267
+ const fetchOptions = {
268
+ method: 'GET',
269
+ credentials: 'include',
270
+ headers: options.headers || {},
271
+ query: options.query || {},
272
+ returnFullRes: options.returnFullRes || false,
273
+ };
274
+
275
+ const res = await this._request(options.url, fetchOptions);
276
+ return res;
277
+ }
278
+
279
+ async _post(options, stringify = true) {
280
+ const fetchOptions = {
281
+ method: 'POST',
282
+ credentials: 'include',
283
+ headers: options.headers || {},
284
+ query: options.query || {},
285
+ body: stringify ? JSON.stringify(options.body) : options.body,
286
+ returnFullRes: options.returnFullRes || false,
287
+ };
288
+ const res = await this._request(options.url, fetchOptions);
289
+ return res;
290
+ }
291
+
292
+ async _patch(options, stringify = true) {
293
+ const fetchOptions = {
294
+ method: 'PATCH',
295
+ credentials: 'include',
296
+ headers: options.headers || {},
297
+ query: options.query || {},
298
+ body: stringify ? JSON.stringify(options.body) : options.body,
299
+ returnFullRes: options.returnFullRes || false,
300
+ };
301
+ const res = await this._request(options.url, fetchOptions);
302
+ return res;
303
+ }
304
+
305
+ async _put(options, stringify = true) {
306
+ const fetchOptions = {
307
+ method: 'PUT',
308
+ credentials: 'include',
309
+ headers: options.headers || {},
310
+ query: options.query || {},
311
+ body: stringify ? JSON.stringify(options.body) : options.body,
312
+ returnFullRes: options.returnFullRes || false,
313
+ };
314
+ const res = await this._request(options.url, fetchOptions);
315
+ return res;
316
+ }
317
+
318
+ async _delete(options) {
319
+ const fetchOptions = {
320
+ method: 'DELETE',
321
+ credentials: 'include',
322
+ headers: options.headers || {},
323
+ query: options.query || {},
324
+ returnFullRes: options.returnFullRes || true,
325
+ };
326
+ return this._request(options.url, fetchOptions);
327
+ }
328
+
329
+ async refreshAuth() {
330
+ throw new Error('refreshAuth not yet defined in child of Requester');
331
+ }
332
+ }
333
+
334
+ module.exports = { Requester };
@@ -1,5 +1,5 @@
1
- const { get } = require('../../assertions');
2
- const { OAuth2Requester } = require('../../module-plugin');
1
+ const { get } = require('../../../assertions');
2
+ const { OAuth2Requester } = require('../..');
3
3
 
4
4
  class Api extends OAuth2Requester {
5
5
  constructor(params) {
@@ -23,7 +23,12 @@ class Api extends OAuth2Requester {
23
23
  return this.authorizationUri;
24
24
  }
25
25
 
26
-
26
+ getAuthorizationRequirements() {
27
+ return {
28
+ url: this.getAuthUri(),
29
+ type: 'oauth2',
30
+ };
31
+ }
27
32
  }
28
33
 
29
34
  module.exports = { Api };
@@ -1,22 +1,26 @@
1
1
  require('dotenv').config();
2
- const {Api} = require('./api');
3
- const {get} = require('../../assertions');
4
- const config = {name: 'anapi'}
2
+ const { Api } = require('./api');
3
+ const { get } = require('../../../assertions');
4
+ const config = { name: 'anapi' }
5
5
 
6
6
  const Definition = {
7
7
  API: Api,
8
- getName: function() {return config.name},
8
+ getAuthorizationRequirements: () => ({
9
+ url: 'http://localhost:3000/redirect/anapi',
10
+ type: 'oauth2',
11
+ }),
12
+ getName: function () { return config.name },
9
13
  moduleName: config.name,
10
14
  modelName: 'AnApi',
11
15
  requiredAuthMethods: {
12
- getToken: async function(api, params){
16
+ getToken: async function (api, params) {
13
17
  const code = get(params.data, 'code');
14
18
  return api.getTokenFromCode(code);
15
19
  },
16
- getEntityDetails: async function(api, callbackParams, tokenResponse, userId) {
20
+ getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
17
21
  const userDetails = await api.getUserDetails();
18
22
  return {
19
- identifiers: { externalId: userDetails.portalId, user: userId },
23
+ identifiers: { externalId: userDetails.portalId, userId },
20
24
  details: { name: userDetails.hub_domain },
21
25
  }
22
26
  },
@@ -26,14 +30,14 @@ const Definition = {
26
30
  ],
27
31
  entity: [],
28
32
  },
29
- getCredentialDetails: async function(api, userId) {
33
+ getCredentialDetails: async function (api, userId) {
30
34
  const userDetails = await api.getUserDetails();
31
35
  return {
32
- identifiers: { externalId: userDetails.portalId, user: userId },
36
+ identifiers: { externalId: userDetails.portalId, userId },
33
37
  details: {}
34
38
  };
35
39
  },
36
- testAuthRequest: async function(api){
40
+ testAuthRequest: async function (api) {
37
41
  return api.getUserDetails()
38
42
  },
39
43
  },
@@ -0,0 +1,16 @@
1
+ class TestModuleFactory {
2
+ constructor() { }
3
+
4
+ async getModuleInstance(entityId, userId) {
5
+ // return minimal stub module with getName and api property
6
+ return {
7
+ getName() { return 'stubModule'; },
8
+ api: {},
9
+ entityId,
10
+ userId,
11
+ testAuth: async () => true,
12
+ };
13
+ }
14
+ }
15
+
16
+ module.exports = { TestModuleFactory };
@@ -0,0 +1,39 @@
1
+ class TestModuleRepository {
2
+ constructor() {
3
+ this.entities = new Map();
4
+ }
5
+
6
+ addEntity(entity) {
7
+ this.entities.set(entity.id, entity);
8
+ }
9
+
10
+ async findEntityById(id) {
11
+ return this.entities.get(id);
12
+ }
13
+
14
+ async findEntitiesByIds(ids) {
15
+ return ids.map((id) => this.entities.get(id));
16
+ }
17
+
18
+ async findEntity(filter) {
19
+ if (!filter || typeof filter !== 'object') {
20
+ return null;
21
+ }
22
+
23
+ if (filter.id && this.entities.has(filter.id)) {
24
+ return this.entities.get(filter.id);
25
+ }
26
+
27
+ if (filter.externalId) {
28
+ for (const entity of this.entities.values()) {
29
+ if (entity.externalId === filter.externalId) {
30
+ return entity;
31
+ }
32
+ }
33
+ }
34
+
35
+ return null;
36
+ }
37
+ }
38
+
39
+ module.exports = { TestModuleRepository };
@@ -0,0 +1,32 @@
1
+ const { Module } = require('../module');
2
+ const { mapModuleClassToModuleDTO } = require('../utils/map-module-dto');
3
+
4
+ class GetEntitiesForUser {
5
+ constructor({ moduleRepository, moduleDefinitions }) {
6
+ this.moduleRepository = moduleRepository;
7
+
8
+ this.definitionMap = new Map();
9
+ for (const definition of moduleDefinitions) {
10
+ this.definitionMap.set(definition.moduleName, definition);
11
+ }
12
+ }
13
+
14
+ async execute(userId) {
15
+ const entities = await this.moduleRepository.findEntitiesByUserId(
16
+ userId
17
+ );
18
+
19
+ return entities.map((entity) => {
20
+ const definition = this.definitionMap.get(entity.moduleName);
21
+
22
+ const moduleInstance = new Module({
23
+ userId,
24
+ definition: definition,
25
+ entity: entity,
26
+ });
27
+ return mapModuleClassToModuleDTO(moduleInstance);
28
+ });
29
+ }
30
+ }
31
+
32
+ module.exports = { GetEntitiesForUser };
@@ -0,0 +1,71 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsById {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
7
+ * @param {} params.moduleDefinitions
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ /**
15
+ * Retrieve entity options for a given entity
16
+ *
17
+ * @param {string|number} entityId - Entity ID to retrieve options for
18
+ * @param {string|number|import('../../user/user').User} userIdOrUser - User ID or User object for validation
19
+ * @returns {Promise<Object>} Entity options
20
+ */
21
+ async execute(entityId, userIdOrUser) {
22
+ // Support both userId (backward compatible) and User object (new pattern)
23
+ const userId = typeof userIdOrUser === 'object' && userIdOrUser?.getId
24
+ ? userIdOrUser.getId()
25
+ : userIdOrUser;
26
+
27
+ const entity = await this.moduleRepository.findEntityById(
28
+ entityId,
29
+ userId
30
+ );
31
+
32
+ if (!entity) {
33
+ throw new Error(`Entity ${entityId} not found`);
34
+ }
35
+
36
+ // Validate entity ownership
37
+ const isOwned = typeof userIdOrUser === 'object' && userIdOrUser?.ownsUserId
38
+ ? userIdOrUser.ownsUserId(entity.userId)
39
+ : entity.userId?.toString() === userId?.toString();
40
+
41
+ if (!isOwned) {
42
+ throw new Error(
43
+ `Entity ${entityId} does not belong to user ${userId}`
44
+ );
45
+ }
46
+
47
+ const entityType = entity.moduleName;
48
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
49
+ const modelName =
50
+ Module.getEntityModelFromDefinition(def).modelName;
51
+ return entityType === modelName;
52
+ });
53
+
54
+ if (!moduleDefinition) {
55
+ throw new Error(
56
+ `Module definition not found for entity type: ${entityType}`
57
+ );
58
+ }
59
+
60
+ const module = new Module({
61
+ userId,
62
+ entity,
63
+ definition: moduleDefinition,
64
+ });
65
+
66
+ const entityOptions = await module.getEntityOptions();
67
+ return entityOptions;
68
+ }
69
+ }
70
+
71
+ module.exports = { GetEntityOptionsById };
@@ -0,0 +1,34 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetEntityOptionsByType {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {} params.moduleDefinitions
7
+ */
8
+ constructor({ moduleDefinitions }) {
9
+ this.moduleDefinitions = moduleDefinitions;
10
+ }
11
+
12
+ /**
13
+ * Retrieve a Module instance for a given user and entity/module type.
14
+ * @param {string} userId
15
+ * @param {string} type – human-readable module/entity type (e.g. "Hubspot")
16
+ */
17
+ async execute(userId, type) {
18
+ const moduleDefinition = this.moduleDefinitions.find(
19
+ (def) => def.getName() === type
20
+ );
21
+ if (!moduleDefinition) {
22
+ throw new Error(`Module definition not found for type: ${type}`);
23
+ }
24
+ const moduleInstance = new Module({
25
+ userId,
26
+ definition: moduleDefinition,
27
+ });
28
+
29
+ const entityOptions = await moduleInstance.getEntityOptions();
30
+ return entityOptions;
31
+ }
32
+ }
33
+
34
+ module.exports = { GetEntityOptionsByType };
@@ -0,0 +1,34 @@
1
+ const { Module } = require('../module');
2
+
3
+ class GetModuleInstanceFromType {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {} params.moduleDefinitions
7
+ */
8
+ constructor({ moduleDefinitions }) {
9
+ this.moduleDefinitions = moduleDefinitions;
10
+ }
11
+
12
+ /**
13
+ * Retrieve a Module instance for a given user and entity/module type.
14
+ * @param {string} userId
15
+ * @param {string} type – human-readable module/entity type (e.g. "Hubspot")
16
+ * @param {Object} [options]
17
+ * @param {string} [options.state] – optional OAuth state value to be forwarded to the API client (round-trips through the OAuth provider).
18
+ */
19
+ async execute(userId, type, options = {}) {
20
+ const moduleDefinition = this.moduleDefinitions.find(
21
+ (def) => def.getName() === type
22
+ );
23
+ if (!moduleDefinition) {
24
+ throw new Error(`Module definition not found for type: ${type}`);
25
+ }
26
+ return new Module({
27
+ userId,
28
+ definition: moduleDefinition,
29
+ state: options.state,
30
+ });
31
+ }
32
+ }
33
+
34
+ module.exports = { GetModuleInstanceFromType };