@friggframework/core 2.0.0-next.11 → 2.0.0-next.110

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 (375) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +999 -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 +171 -0
  7. package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
  8. package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -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 +359 -0
  16. package/application/commands/credential-commands.js +262 -0
  17. package/application/commands/entity-commands.js +336 -0
  18. package/application/commands/integration-commands.js +356 -0
  19. package/application/commands/integration-mapping-commands.js +25 -0
  20. package/application/commands/process-commands.js +135 -0
  21. package/application/commands/report-commands.js +188 -0
  22. package/application/commands/scheduler-commands.js +263 -0
  23. package/application/commands/usage-commands.js +56 -0
  24. package/application/commands/user-commands.js +312 -0
  25. package/application/index.js +83 -0
  26. package/artifacts/repositories/artifact-repository-factory.js +19 -0
  27. package/artifacts/repositories/artifact-repository-interface.js +27 -0
  28. package/artifacts/repositories/artifact-repository-local.js +42 -0
  29. package/artifacts/repositories/artifact-repository-s3.js +61 -0
  30. package/assertions/index.js +0 -3
  31. package/core/CLAUDE.md +700 -0
  32. package/core/Worker.js +60 -24
  33. package/core/create-handler.js +189 -5
  34. package/core/parameters-to-env.js +257 -0
  35. package/core/ssm-preload.mjs +34 -0
  36. package/credential/repositories/credential-active-type.js +32 -0
  37. package/credential/repositories/credential-repository-documentdb.js +355 -0
  38. package/credential/repositories/credential-repository-factory.js +54 -0
  39. package/credential/repositories/credential-repository-interface.js +113 -0
  40. package/credential/repositories/credential-repository-mongo.js +294 -0
  41. package/credential/repositories/credential-repository-postgres.js +312 -0
  42. package/credential/repositories/credential-repository.js +300 -0
  43. package/credential/use-cases/get-credential-for-user.js +25 -0
  44. package/credential/use-cases/update-authentication-status.js +15 -0
  45. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  46. package/database/adapters/lambda-invoker.js +97 -0
  47. package/database/config.js +154 -0
  48. package/database/documentdb-encryption-service.js +330 -0
  49. package/database/documentdb-utils.js +192 -0
  50. package/database/encryption/README.md +839 -0
  51. package/database/encryption/documentdb-encryption-service.md +3575 -0
  52. package/database/encryption/encryption-schema-registry.js +401 -0
  53. package/database/encryption/field-encryption-service.js +254 -0
  54. package/database/encryption/logger.js +79 -0
  55. package/database/encryption/prisma-encryption-extension.js +230 -0
  56. package/database/index.js +21 -21
  57. package/database/prisma.js +182 -0
  58. package/database/repositories/health-check-repository-documentdb.js +138 -0
  59. package/database/repositories/health-check-repository-factory.js +48 -0
  60. package/database/repositories/health-check-repository-interface.js +82 -0
  61. package/database/repositories/health-check-repository-mongodb.js +89 -0
  62. package/database/repositories/health-check-repository-postgres.js +82 -0
  63. package/database/repositories/migration-status-repository-s3.js +137 -0
  64. package/database/use-cases/check-database-health-use-case.js +29 -0
  65. package/database/use-cases/check-database-state-use-case.js +81 -0
  66. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  67. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  68. package/database/use-cases/get-migration-status-use-case.js +93 -0
  69. package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
  70. package/database/use-cases/run-database-migration-use-case.js +139 -0
  71. package/database/use-cases/test-encryption-use-case.js +253 -0
  72. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  73. package/database/utils/mongodb-collection-utils.js +94 -0
  74. package/database/utils/mongodb-schema-init.js +108 -0
  75. package/database/utils/prisma-runner.js +491 -0
  76. package/database/utils/prisma-schema-parser.js +182 -0
  77. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  78. package/encrypt/Cryptor.js +34 -168
  79. package/encrypt/index.js +1 -2
  80. package/errors/client-safe-error.js +26 -0
  81. package/errors/fetch-error.js +15 -7
  82. package/errors/index.js +2 -0
  83. package/generated/prisma-mongodb/client.d.ts +1 -0
  84. package/generated/prisma-mongodb/client.js +5 -0
  85. package/generated/prisma-mongodb/default.d.ts +1 -0
  86. package/generated/prisma-mongodb/default.js +5 -0
  87. package/generated/prisma-mongodb/edge.d.ts +1 -0
  88. package/generated/prisma-mongodb/edge.js +386 -0
  89. package/generated/prisma-mongodb/index-browser.js +368 -0
  90. package/generated/prisma-mongodb/index.d.ts +27065 -0
  91. package/generated/prisma-mongodb/index.js +411 -0
  92. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  93. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  94. package/generated/prisma-mongodb/package.json +183 -0
  95. package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
  96. package/generated/prisma-mongodb/runtime/edge.js +35 -0
  97. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  98. package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
  99. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  100. package/generated/prisma-mongodb/runtime/library.js +147 -0
  101. package/generated/prisma-mongodb/runtime/react-native.js +84 -0
  102. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
  103. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
  104. package/generated/prisma-mongodb/schema.prisma +455 -0
  105. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
  106. package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
  107. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  108. package/generated/prisma-mongodb/wasm.js +393 -0
  109. package/generated/prisma-postgresql/client.d.ts +1 -0
  110. package/generated/prisma-postgresql/client.js +5 -0
  111. package/generated/prisma-postgresql/default.d.ts +1 -0
  112. package/generated/prisma-postgresql/default.js +5 -0
  113. package/generated/prisma-postgresql/edge.d.ts +1 -0
  114. package/generated/prisma-postgresql/edge.js +407 -0
  115. package/generated/prisma-postgresql/index-browser.js +389 -0
  116. package/generated/prisma-postgresql/index.d.ts +29904 -0
  117. package/generated/prisma-postgresql/index.js +432 -0
  118. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  119. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  120. package/generated/prisma-postgresql/package.json +183 -0
  121. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  122. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  123. package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
  124. package/generated/prisma-postgresql/runtime/edge.js +35 -0
  125. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  126. package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
  127. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  128. package/generated/prisma-postgresql/runtime/library.js +147 -0
  129. package/generated/prisma-postgresql/runtime/react-native.js +84 -0
  130. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
  131. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
  132. package/generated/prisma-postgresql/schema.prisma +436 -0
  133. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
  134. package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
  135. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  136. package/generated/prisma-postgresql/wasm.js +414 -0
  137. package/handlers/WEBHOOKS.md +653 -0
  138. package/handlers/app-definition-loader.js +67 -0
  139. package/handlers/app-handler-helpers.js +3 -3
  140. package/handlers/backend-utils.js +271 -42
  141. package/handlers/database-migration-handler.js +227 -0
  142. package/handlers/integration-event-dispatcher.js +68 -0
  143. package/handlers/middleware/admin-auth.js +73 -0
  144. package/handlers/routers/HEALTHCHECK.md +342 -0
  145. package/handlers/routers/auth.js +4 -15
  146. package/handlers/routers/db-migration.handler.js +29 -0
  147. package/handlers/routers/db-migration.js +329 -0
  148. package/handlers/routers/health.js +518 -0
  149. package/handlers/routers/integration-defined-routers.js +85 -10
  150. package/handlers/routers/integration-webhook-routers.js +80 -0
  151. package/handlers/routers/user.js +27 -5
  152. package/handlers/routers/websocket.js +5 -3
  153. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  154. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  155. package/handlers/workers/db-migration.js +427 -0
  156. package/handlers/workers/dlq-processor.js +63 -0
  157. package/handlers/workers/integration-defined-workers.js +13 -7
  158. package/index.js +102 -46
  159. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  160. package/infrastructure/scheduler/index.js +33 -0
  161. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  162. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  163. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  164. package/integrations/EXTENSIONS.md +240 -0
  165. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  166. package/integrations/extension.js +254 -0
  167. package/integrations/index.js +20 -10
  168. package/integrations/integration-base.js +730 -74
  169. package/integrations/integration-router.js +398 -180
  170. package/integrations/options.js +1 -1
  171. package/integrations/repositories/config-patch-shared.js +43 -0
  172. package/integrations/repositories/integration-mapping-repository-documentdb.js +303 -0
  173. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  174. package/integrations/repositories/integration-mapping-repository-interface.js +120 -0
  175. package/integrations/repositories/integration-mapping-repository-mongo.js +183 -0
  176. package/integrations/repositories/integration-mapping-repository-postgres.js +255 -0
  177. package/integrations/repositories/integration-mapping-repository.js +156 -0
  178. package/integrations/repositories/integration-repository-documentdb.js +322 -0
  179. package/integrations/repositories/integration-repository-factory.js +51 -0
  180. package/integrations/repositories/integration-repository-interface.js +189 -0
  181. package/integrations/repositories/integration-repository-mongo.js +450 -0
  182. package/integrations/repositories/integration-repository-postgres.js +492 -0
  183. package/integrations/repositories/process-repository-documentdb.js +311 -0
  184. package/integrations/repositories/process-repository-factory.js +53 -0
  185. package/integrations/repositories/process-repository-interface.js +136 -0
  186. package/integrations/repositories/process-repository-mongo.js +262 -0
  187. package/integrations/repositories/process-repository-postgres.js +380 -0
  188. package/integrations/repositories/process-update-ops-shared.js +112 -0
  189. package/integrations/repositories/report-id.js +13 -0
  190. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  191. package/integrations/tests/doubles/dummy-integration-class.js +113 -0
  192. package/integrations/tests/doubles/test-integration-repository.js +146 -0
  193. package/integrations/use-cases/create-integration.js +215 -0
  194. package/integrations/use-cases/create-process.js +130 -0
  195. package/integrations/use-cases/delete-integration-for-user.js +119 -0
  196. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  197. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  198. package/integrations/use-cases/get-integration-for-user.js +78 -0
  199. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  200. package/integrations/use-cases/get-integration-instance.js +83 -0
  201. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  202. package/integrations/use-cases/get-possible-integrations.js +27 -0
  203. package/integrations/use-cases/get-process.js +89 -0
  204. package/integrations/use-cases/index.js +19 -0
  205. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  206. package/integrations/use-cases/load-integration-context.js +71 -0
  207. package/integrations/use-cases/patch-integration-config.js +39 -0
  208. package/integrations/use-cases/process-errors.js +28 -0
  209. package/integrations/use-cases/update-integration-config.js +32 -0
  210. package/integrations/use-cases/update-integration-messages.js +44 -0
  211. package/integrations/use-cases/update-integration-status.js +32 -0
  212. package/integrations/use-cases/update-integration.js +92 -0
  213. package/integrations/use-cases/update-process-metrics.js +222 -0
  214. package/integrations/use-cases/update-process-state.js +163 -0
  215. package/integrations/utils/map-integration-dto.js +37 -0
  216. package/jest-global-setup-noop.js +3 -0
  217. package/jest-global-teardown-noop.js +3 -0
  218. package/logs/logger.js +0 -4
  219. package/{module-plugin → modules}/index.js +0 -10
  220. package/modules/module-factory.js +56 -0
  221. package/modules/module.js +307 -0
  222. package/modules/repositories/module-repository-documentdb.js +350 -0
  223. package/modules/repositories/module-repository-factory.js +40 -0
  224. package/modules/repositories/module-repository-interface.js +145 -0
  225. package/modules/repositories/module-repository-mongo.js +436 -0
  226. package/modules/repositories/module-repository-postgres.js +481 -0
  227. package/modules/repositories/module-repository.js +369 -0
  228. package/modules/requester/api-key.js +52 -0
  229. package/modules/requester/oauth-2.js +555 -0
  230. package/modules/requester/requester.js +542 -0
  231. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  232. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  233. package/modules/tests/doubles/test-module-factory.js +16 -0
  234. package/modules/tests/doubles/test-module-repository.js +39 -0
  235. package/modules/use-cases/get-entities-for-user.js +32 -0
  236. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  237. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  238. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  239. package/modules/use-cases/get-module.js +74 -0
  240. package/modules/use-cases/process-authorization-callback.js +243 -0
  241. package/modules/use-cases/refresh-entity-options.js +72 -0
  242. package/modules/use-cases/test-module-auth.js +72 -0
  243. package/modules/utils/map-module-dto.js +18 -0
  244. package/package.json +92 -50
  245. package/prisma-mongodb/schema.prisma +455 -0
  246. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  247. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  248. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  249. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  250. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  251. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  252. package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
  253. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  254. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  255. package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
  256. package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
  257. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  258. package/prisma-postgresql/schema.prisma +436 -0
  259. package/queues/queuer-util.js +103 -21
  260. package/reporting/README.md +154 -0
  261. package/reporting/builtin-reports.js +6 -0
  262. package/reporting/index.js +13 -0
  263. package/reporting/report-base.js +49 -0
  264. package/reporting/reports/integrations-report.js +221 -0
  265. package/syncs/manager.js +468 -443
  266. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  267. package/syncs/repositories/sync-repository-factory.js +43 -0
  268. package/syncs/repositories/sync-repository-interface.js +109 -0
  269. package/syncs/repositories/sync-repository-mongo.js +239 -0
  270. package/syncs/repositories/sync-repository-postgres.js +319 -0
  271. package/syncs/sync.js +0 -1
  272. package/telemetry/README.md +331 -0
  273. package/telemetry/bind-telemetry-context.js +73 -0
  274. package/telemetry/canonical-counters.js +52 -0
  275. package/telemetry/exporters.js +85 -0
  276. package/telemetry/index.js +26 -0
  277. package/telemetry/instrument-handler.js +87 -0
  278. package/telemetry/no-op-telemetry.js +67 -0
  279. package/telemetry/north-star.js +103 -0
  280. package/telemetry/otel-telemetry.js +213 -0
  281. package/telemetry/plugin-subscribers.js +77 -0
  282. package/telemetry/telemetry-config.js +120 -0
  283. package/telemetry/telemetry-context.js +40 -0
  284. package/telemetry/telemetry-event-bus.js +58 -0
  285. package/telemetry/telemetry-runtime.js +147 -0
  286. package/telemetry/telemetry-service.js +51 -0
  287. package/telemetry/usage-rollup-subscriber.js +116 -0
  288. package/token/repositories/token-repository-documentdb.js +137 -0
  289. package/token/repositories/token-repository-factory.js +40 -0
  290. package/token/repositories/token-repository-interface.js +131 -0
  291. package/token/repositories/token-repository-mongo.js +219 -0
  292. package/token/repositories/token-repository-postgres.js +264 -0
  293. package/token/repositories/token-repository.js +219 -0
  294. package/types/associations/index.d.ts +0 -17
  295. package/types/core/index.d.ts +12 -4
  296. package/types/database/index.d.ts +10 -2
  297. package/types/encrypt/index.d.ts +5 -3
  298. package/types/integrations/index.d.ts +3 -8
  299. package/types/module-plugin/index.d.ts +20 -69
  300. package/types/syncs/index.d.ts +0 -17
  301. package/usage/README.md +54 -0
  302. package/usage/index.js +17 -0
  303. package/usage/repositories/usage-repository-documentdb.js +194 -0
  304. package/usage/repositories/usage-repository-factory.js +25 -0
  305. package/usage/repositories/usage-repository-interface.js +37 -0
  306. package/usage/repositories/usage-repository-prisma.js +146 -0
  307. package/usage/tracked-metrics.js +38 -0
  308. package/usage/usage-windows.js +24 -0
  309. package/user/repositories/user-repository-documentdb.js +458 -0
  310. package/user/repositories/user-repository-factory.js +52 -0
  311. package/user/repositories/user-repository-interface.js +214 -0
  312. package/user/repositories/user-repository-mongo.js +323 -0
  313. package/user/repositories/user-repository-postgres.js +377 -0
  314. package/user/tests/doubles/test-user-repository.js +72 -0
  315. package/user/use-cases/authenticate-user.js +127 -0
  316. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  317. package/user/use-cases/create-individual-user.js +61 -0
  318. package/user/use-cases/create-organization-user.js +47 -0
  319. package/user/use-cases/create-token-for-user-id.js +30 -0
  320. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  321. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  322. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  323. package/user/use-cases/login-user.js +122 -0
  324. package/user/user.js +125 -0
  325. package/utils/backend-path.js +38 -0
  326. package/utils/index.js +6 -0
  327. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  328. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  329. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  330. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  331. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  332. package/websocket/repositories/websocket-connection-repository.js +161 -0
  333. package/assertions/is-equal.js +0 -17
  334. package/associations/model.js +0 -54
  335. package/database/models/IndividualUser.js +0 -76
  336. package/database/models/OrganizationUser.js +0 -29
  337. package/database/models/State.js +0 -9
  338. package/database/models/Token.js +0 -70
  339. package/database/models/UserModel.js +0 -7
  340. package/database/models/WebsocketConnection.js +0 -49
  341. package/database/mongo.js +0 -45
  342. package/database/mongoose.js +0 -5
  343. package/encrypt/Cryptor.test.js +0 -32
  344. package/encrypt/encrypt.js +0 -132
  345. package/encrypt/encrypt.test.js +0 -1069
  346. package/encrypt/test-encrypt.js +0 -107
  347. package/errors/base-error.test.js +0 -32
  348. package/errors/fetch-error.test.js +0 -79
  349. package/errors/halt-error.test.js +0 -11
  350. package/errors/validation-errors.test.js +0 -120
  351. package/handlers/routers/middleware/loadUser.js +0 -15
  352. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  353. package/integrations/create-frigg-backend.js +0 -31
  354. package/integrations/integration-factory.js +0 -251
  355. package/integrations/integration-mapping.js +0 -43
  356. package/integrations/integration-model.js +0 -46
  357. package/integrations/integration-user.js +0 -144
  358. package/integrations/test/integration-base.test.js +0 -144
  359. package/lambda/TimeoutCatcher.test.js +0 -68
  360. package/logs/logger.test.js +0 -76
  361. package/module-plugin/auther.js +0 -393
  362. package/module-plugin/credential.js +0 -22
  363. package/module-plugin/entity-manager.js +0 -70
  364. package/module-plugin/entity.js +0 -46
  365. package/module-plugin/manager.js +0 -169
  366. package/module-plugin/module-factory.js +0 -61
  367. package/module-plugin/requester/api-key.js +0 -36
  368. package/module-plugin/requester/oauth-2.js +0 -219
  369. package/module-plugin/requester/requester.js +0 -165
  370. package/module-plugin/requester/requester.test.js +0 -28
  371. package/module-plugin/test/auther.test.js +0 -97
  372. package/syncs/model.js +0 -62
  373. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  374. /package/{module-plugin → modules}/requester/basic.js +0 -0
  375. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,542 @@
1
+ const fetch = require('node-fetch');
2
+ const { AsyncLocalStorage } = require('async_hooks');
3
+ const { Delegate } = require('../../core');
4
+ const { FetchError } = require('../../errors');
5
+ const { get } = require('../../assertions');
6
+ const { getTelemetry } = require('../../telemetry/telemetry-runtime');
7
+
8
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
9
+ const MAX_AUTH_RETRIES = 3;
10
+
11
+ // This context marks the async call chain that holds the refresh slot, and
12
+ // names the requester that holds it. Token requests re-enter _rawRequest
13
+ // through this._post. A 401 from inside that chain must fail fast: if it
14
+ // joins the refresh in flight, it awaits its own promise and hangs. The
15
+ // identity keeps a second requester used inside the chain out of the check.
16
+ // AsyncLocalStorage reaches the nested calls without help from the subclasses.
17
+ const refreshContext = new AsyncLocalStorage();
18
+
19
+ class Requester extends Delegate {
20
+ constructor(params) {
21
+ super(params);
22
+ this.backOff = get(params, 'backOff', [1, 3, 10, 30, 60, 180]);
23
+ this.isRefreshable = false;
24
+ this.refreshCount = 0;
25
+ this.authGraceRetryCount = 0;
26
+ // Concurrent 401s share one refreshAuth() run. See _refreshAuthOnce().
27
+ this._inFlightRefresh = null;
28
+ // This counter increases when the tokens change. A stale 401 (the
29
+ // token changed already) then retries and does not refresh again.
30
+ this._authGeneration = 0;
31
+ this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
32
+ this.delegateTypes.push(this.DLGT_INVALID_AUTH);
33
+ this.agent = get(params, 'agent', null);
34
+
35
+ // Per-attempt HTTP timeout. Without this the framework called fetch()
36
+ // with no AbortController and no timeout — a silently-hung TCP
37
+ // connection (server accepts but never responds) blocked the calling
38
+ // promise forever, cascading into stalled batches, stalled syncs,
39
+ // and worker-lambda timeouts.
40
+ //
41
+ // Configuration precedence:
42
+ // 1. Instance param: new Requester({ requestTimeoutMs: 30_000 })
43
+ // 2. Class static: static requestTimeoutMs = 30_000
44
+ // 3. Default: DEFAULT_REQUEST_TIMEOUT_MS (60s)
45
+ //
46
+ // Pass 0 (or null) to disable the timeout entirely — reserved for
47
+ // test doubles and documented long-running endpoints.
48
+ // Intentionally NOT using `get(params, ...)` here — the Frigg
49
+ // `get` helper throws RequiredPropertyError if the key is missing
50
+ // and no default is provided, which would collide with the fall-
51
+ // through to the class-level static override.
52
+ const instanceTimeout = params?.requestTimeoutMs;
53
+ this.requestTimeoutMs =
54
+ instanceTimeout !== undefined && instanceTimeout !== null
55
+ ? instanceTimeout
56
+ : this.constructor.requestTimeoutMs ??
57
+ DEFAULT_REQUEST_TIMEOUT_MS;
58
+
59
+ // Allow passing in the fetch function
60
+ // Instance methods can use this.fetch without differentiating
61
+ this.fetch = get(params, 'fetch', fetch);
62
+
63
+ // Defaults to the process singleton. Pass an integration's bound
64
+ // `this.telemetry` to attribute out-of-band requests — setup/OAuth calls
65
+ // made before an integration context exists aren't rolled up otherwise.
66
+ this.telemetry = (params && params.telemetry) || getTelemetry();
67
+ }
68
+
69
+ /**
70
+ * Redact secrets/PII from a URL before it touches telemetry. Many API
71
+ * modules embed credentials in the query string (?api_key=, ?token=,
72
+ * presigned signatures) or in userinfo — those must never reach a span,
73
+ * the bus, or an exporter. Keep only protocol + host + path (enough for
74
+ * North Star endpoint matching).
75
+ */
76
+ _sanitizeUrl(url) {
77
+ const raw = String(url);
78
+ try {
79
+ const u = new URL(raw);
80
+ return `${u.protocol}//${u.host}${u.pathname}`;
81
+ } catch (_) {
82
+ // Relative/opaque URL: drop the query string at minimum.
83
+ return raw.split('?')[0];
84
+ }
85
+ }
86
+
87
+ /** Bounded module label for the apimodule.requests metric. */
88
+ _telemetryModuleLabel() {
89
+ return (
90
+ this.moduleName ||
91
+ this.delegate?.name ||
92
+ this.delegate?.constructor?.name ||
93
+ this.constructor?.name ||
94
+ 'unknown'
95
+ );
96
+ }
97
+
98
+ parsedBody = async (resp) => {
99
+ const contentType = resp.headers.get('Content-Type') || '';
100
+
101
+ if (
102
+ contentType.match(/^application\/json/) ||
103
+ contentType.match(/^application\/vnd.api\+json/) ||
104
+ contentType.match(/^application\/hal\+json/)
105
+ ) {
106
+ return resp.json();
107
+ }
108
+
109
+ return resp.text();
110
+ };
111
+
112
+ /**
113
+ * Instrumenting entry point. Wraps the whole logical request —
114
+ * including retry/refresh recursion — in a single span + one
115
+ * `frigg.apimodule.requests` counter, emitted on the `attempt === 0`
116
+ * boundary so retries are never double-counted. The full URL rides the
117
+ * span only; the metric carries bounded labels {module, method, status}
118
+ * — never endpoint.
119
+ *
120
+ * @param {string} url - The request URL, relative or absolute.
121
+ * @param {Object} options - Fetch options (method, headers, body, query,
122
+ * returnFullRes, etc.) built by the `_get`/`_post`/`_patch`/`_put`/
123
+ * `_delete` wrappers.
124
+ * @param {number} attempt - 0-based count of retries already made for
125
+ * this call. Non-zero only if a caller re-enters directly; normal
126
+ * retries recurse through `_rawRequest` instead (see below).
127
+ */
128
+ async _request(url, options = {}, attempt = 0) {
129
+ if (attempt !== 0) {
130
+ return this._rawRequest(url, options, attempt);
131
+ }
132
+
133
+ const telemetry = this.telemetry;
134
+ if (!telemetry || typeof telemetry.span !== 'function') {
135
+ return this._rawRequest(url, options, 0);
136
+ }
137
+
138
+ const module = this._telemetryModuleLabel();
139
+ const method = (options.method || 'GET').toUpperCase();
140
+ const safeUrl = this._sanitizeUrl(url);
141
+
142
+ return telemetry.span('frigg.apimodule.request', async (span) => {
143
+ if (span && typeof span.setAttributes === 'function') {
144
+ span.setAttributes({
145
+ 'frigg.module': module,
146
+ 'http.request.method': method,
147
+ // Redacted (no query/userinfo) — never emit raw URLs.
148
+ 'url.path': safeUrl,
149
+ });
150
+ }
151
+ // Redacted url (unbounded) rides the bus-only context for North Star
152
+ // derived-from-trace matching — never a metric label.
153
+ const busContext = { url: safeUrl };
154
+ try {
155
+ const result = await this._rawRequest(url, options, 0);
156
+ telemetry.count(
157
+ 'frigg.apimodule.requests',
158
+ 1,
159
+ { module, method, status: 'ok' },
160
+ busContext
161
+ );
162
+ return result;
163
+ } catch (err) {
164
+ const code = err?.status ?? err?.statusCode;
165
+ const status = code ? String(code) : 'error';
166
+ if (span && typeof span.setAttribute === 'function' && code) {
167
+ span.setAttribute('http.response.status_code', code);
168
+ }
169
+ telemetry.count(
170
+ 'frigg.apimodule.requests',
171
+ 1,
172
+ { module, method, status },
173
+ busContext
174
+ );
175
+ throw err;
176
+ }
177
+ });
178
+ }
179
+
180
+ /**
181
+ * @param {string} url - The request URL, relative or absolute.
182
+ * @param {Object} options - Fetch options, as built by `_request`.
183
+ * @param {number} attempt - 0-based count of retries already made for
184
+ * this call. Indexes `this.backOff` for the next delay and is passed
185
+ * back in on each recursive retry.
186
+ */
187
+ async _rawRequest(url, options, attempt = 0) {
188
+ let encodedUrl = encodeURI(url);
189
+ if (options.query) {
190
+ let queryBuild = '?';
191
+ for (const key in options.query) {
192
+ queryBuild += `${encodeURIComponent(key)}=${encodeURIComponent(
193
+ options.query[key]
194
+ )}&`;
195
+ }
196
+ encodedUrl += queryBuild.slice(0, -1);
197
+ }
198
+
199
+ options.headers = await this.addAuthHeaders(options.headers);
200
+
201
+ // A 401 that arrives after a concurrent refresh is stale. It is not
202
+ // proof that the new token failed.
203
+ const authGenerationAtDispatch = this._authGeneration;
204
+
205
+ if (this.agent) options.agent = this.agent;
206
+
207
+ // Per-attempt timeout — fresh AbortController per call so the retry
208
+ // recursion (with its own backoff sleeps) always gets a clean
209
+ // signal. Timer is cleared in the finally block regardless of
210
+ // outcome.
211
+ const timeoutMs = this.requestTimeoutMs;
212
+ const controller = timeoutMs > 0 ? new AbortController() : null;
213
+ const timeoutHandle = controller
214
+ ? setTimeout(() => controller.abort(), timeoutMs)
215
+ : null;
216
+ const fetchOptions = controller
217
+ ? { ...options, signal: controller.signal }
218
+ : options;
219
+
220
+ // Timer must stay active through body consumption. node-fetch v2
221
+ // resolves the fetch() promise when headers arrive, not when the
222
+ // body is fully read — so a server that sends headers and then
223
+ // stalls the body would still hang parsedBody() or
224
+ // FetchError.create()'s response.text() call. We clear the timer
225
+ // only after the body is fully consumed (success path) or
226
+ // deliberately before each recursive retry so the new attempt
227
+ // starts with its own fresh timer.
228
+ let timerCleared = false;
229
+ const clearRequestTimer = () => {
230
+ if (!timerCleared && timeoutHandle) {
231
+ clearTimeout(timeoutHandle);
232
+ timerCleared = true;
233
+ }
234
+ };
235
+
236
+ try {
237
+ let response;
238
+ try {
239
+ response = await this.fetch(encodedUrl, fetchOptions);
240
+ } catch (e) {
241
+ // AbortController fires AbortError (name) / ETIMEDOUT-shaped
242
+ // errors (type on node-fetch) when we hit the timeout. No
243
+ // retry on timeout: a slow endpoint is a downstream problem,
244
+ // and each retry would wait another `timeoutMs` before giving
245
+ // up — amplifying the hang into a per-record multi-minute
246
+ // stall at batch scale.
247
+ const isTimeout =
248
+ e?.name === 'AbortError' || e?.type === 'aborted';
249
+ if (e?.code === 'ECONNRESET' && attempt < this.backOff.length) {
250
+ clearRequestTimer();
251
+ const delay = this.backOff[attempt] * 1000;
252
+ await new Promise((resolve) => setTimeout(resolve, delay));
253
+ return this._rawRequest(url, options, attempt + 1);
254
+ }
255
+ const fetchError = await FetchError.create({
256
+ resource: encodedUrl,
257
+ init: options,
258
+ responseBody: isTimeout
259
+ ? `Request timed out after ${timeoutMs}ms`
260
+ : e,
261
+ });
262
+ if (isTimeout) {
263
+ // Flag + machine-readable fields so callers can
264
+ // distinguish a timeout from a generic network error
265
+ // without parsing the message (which FetchError
266
+ // sanitizes outside of STAGE=dev).
267
+ fetchError.isTimeout = true;
268
+ fetchError.timeoutMs = timeoutMs;
269
+ }
270
+ throw fetchError;
271
+ }
272
+
273
+ const { status } = response;
274
+
275
+ // If the status is retriable and there are back off requests left, retry the request
276
+ if (
277
+ (status === 429 || status >= 500) &&
278
+ attempt < this.backOff.length
279
+ ) {
280
+ clearRequestTimer();
281
+ const delay = this.backOff[attempt] * 1000;
282
+ await new Promise((resolve) => setTimeout(resolve, delay));
283
+ return this._rawRequest(url, options, attempt + 1);
284
+ }
285
+
286
+ if (status === 401) {
287
+ // A 401 from inside the refresh flow means the provider
288
+ // rejected the credential itself (invalid_client). A new
289
+ // refresh cannot help. A join would await this same call.
290
+ if (this._isInsideRefreshFlow()) {
291
+ throw await this._invalidateAuth(
292
+ encodedUrl,
293
+ options,
294
+ response
295
+ );
296
+ }
297
+
298
+ const tokenReplacedWhileInFlight =
299
+ this._authGeneration !== authGenerationAtDispatch;
300
+ if (this.isRefreshable && tokenReplacedWhileInFlight) {
301
+ // This request did not try the current token. Retry with
302
+ // it. Do not spend one more provider-side rotation.
303
+ clearRequestTimer();
304
+ return this._rawRequest(url, options, attempt + 1);
305
+ }
306
+
307
+ if (!this.isRefreshable) {
308
+ // Up to MAX_AUTH_RETRIES grace retries before invalidating
309
+ // — a 401 alone isn't proof the credential is bad.
310
+ if (
311
+ this.authGraceRetryCount < MAX_AUTH_RETRIES &&
312
+ this.authGraceRetryCount < this.backOff.length
313
+ ) {
314
+ const delay =
315
+ this.backOff[this.authGraceRetryCount] * 1000;
316
+ this.authGraceRetryCount++;
317
+ clearRequestTimer();
318
+ await new Promise((resolve) =>
319
+ setTimeout(resolve, delay)
320
+ );
321
+ return this._rawRequest(url, options, attempt + 1);
322
+ }
323
+
324
+ throw await this._invalidateAuth(
325
+ encodedUrl,
326
+ options,
327
+ response
328
+ );
329
+ }
330
+
331
+ // Concurrent 401s share one refresh. Independent refreshes
332
+ // kill each other, because many providers use single-use
333
+ // refresh tokens. Only the initiator spends the retry budget.
334
+ const refreshAlreadyInFlight = Boolean(this._inFlightRefresh);
335
+
336
+ if (
337
+ !refreshAlreadyInFlight &&
338
+ this.refreshCount >= MAX_AUTH_RETRIES
339
+ ) {
340
+ throw await this._invalidateAuth(
341
+ encodedUrl,
342
+ options,
343
+ response
344
+ );
345
+ }
346
+
347
+ if (!refreshAlreadyInFlight) {
348
+ this.refreshCount++;
349
+ }
350
+
351
+ const refreshSucceeded = await this._refreshAuthOnce();
352
+ if (refreshSucceeded) {
353
+ clearRequestTimer();
354
+ return this._rawRequest(url, options, attempt + 1);
355
+ }
356
+
357
+ throw await this._invalidateAuth(encodedUrl, options, response);
358
+ }
359
+
360
+ // If the error wasn't retried, throw. FetchError.create reads
361
+ // the response body (response.text()) — timer must still be
362
+ // alive to catch a stalled body stream.
363
+ if (status >= 400) {
364
+ const fetchError = await FetchError.create({
365
+ resource: encodedUrl,
366
+ init: options,
367
+ response,
368
+ });
369
+ throw this._maybeFlagTimeoutDuringBodyRead(
370
+ fetchError,
371
+ timeoutMs
372
+ );
373
+ }
374
+
375
+ // Successful response: reset the per-instance refresh budget so
376
+ // a later 401 in the same Requester lifetime can attempt refresh
377
+ // again instead of silently falling through.
378
+ this.refreshCount = 0;
379
+ this.authGraceRetryCount = 0;
380
+
381
+ // parsedBody consumes the response body stream. If the server
382
+ // stalls mid-stream the timer (still armed) aborts it.
383
+ return options.returnFullRes
384
+ ? response
385
+ : await this.parsedBody(response);
386
+ } catch (e) {
387
+ // If the abort fired during body consumption, node-fetch emits
388
+ // the error as an AbortError on the body stream. Surface the
389
+ // same isTimeout flag callers use for header-phase timeouts.
390
+ throw this._maybeFlagTimeoutDuringBodyRead(e, timeoutMs);
391
+ } finally {
392
+ clearRequestTimer();
393
+ }
394
+ }
395
+
396
+ async _invalidateAuth(encodedUrl, options, response) {
397
+ const fetchError = await FetchError.create({
398
+ resource: encodedUrl,
399
+ init: options,
400
+ response,
401
+ });
402
+ await this.notify(this.DLGT_INVALID_AUTH, fetchError);
403
+ return fetchError;
404
+ }
405
+
406
+ _maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
407
+ if (!err || typeof err !== 'object') return err;
408
+ if (err.isTimeout) return err;
409
+ const isAbort = err.name === 'AbortError' || err.type === 'aborted';
410
+ if (!isAbort) return err;
411
+ err.isTimeout = true;
412
+ err.timeoutMs = timeoutMs;
413
+ return err;
414
+ }
415
+
416
+ async _get(options) {
417
+ const fetchOptions = {
418
+ method: 'GET',
419
+ credentials: 'include',
420
+ headers: options.headers || {},
421
+ query: options.query || {},
422
+ returnFullRes: options.returnFullRes || false,
423
+ };
424
+
425
+ const res = await this._request(options.url, fetchOptions);
426
+ return res;
427
+ }
428
+
429
+ async _post(options, stringify = true) {
430
+ const fetchOptions = {
431
+ method: 'POST',
432
+ credentials: 'include',
433
+ headers: options.headers || {},
434
+ query: options.query || {},
435
+ body: stringify ? JSON.stringify(options.body) : options.body,
436
+ returnFullRes: options.returnFullRes || false,
437
+ };
438
+ const res = await this._request(options.url, fetchOptions);
439
+ return res;
440
+ }
441
+
442
+ async _patch(options, stringify = true) {
443
+ const fetchOptions = {
444
+ method: 'PATCH',
445
+ credentials: 'include',
446
+ headers: options.headers || {},
447
+ query: options.query || {},
448
+ body: stringify ? JSON.stringify(options.body) : options.body,
449
+ returnFullRes: options.returnFullRes || false,
450
+ };
451
+ const res = await this._request(options.url, fetchOptions);
452
+ return res;
453
+ }
454
+
455
+ async _put(options, stringify = true) {
456
+ const fetchOptions = {
457
+ method: 'PUT',
458
+ credentials: 'include',
459
+ headers: options.headers || {},
460
+ query: options.query || {},
461
+ body: stringify ? JSON.stringify(options.body) : options.body,
462
+ returnFullRes: options.returnFullRes || false,
463
+ };
464
+ const res = await this._request(options.url, fetchOptions);
465
+ return res;
466
+ }
467
+
468
+ async _delete(options) {
469
+ const fetchOptions = {
470
+ method: 'DELETE',
471
+ credentials: 'include',
472
+ headers: options.headers || {},
473
+ query: options.query || {},
474
+ returnFullRes: options.returnFullRes || true,
475
+ };
476
+ return this._request(options.url, fetchOptions);
477
+ }
478
+
479
+ /**
480
+ * Runs one refreshAuth() at a time. The first caller starts the refresh.
481
+ * Callers that arrive during the refresh await the same promise. The
482
+ * check-and-store step is synchronous. Thus two concurrent callers
483
+ * cannot both start a refresh.
484
+ *
485
+ * @returns {Promise<boolean>} True if the refresh succeeded.
486
+ */
487
+ _refreshAuthOnce() {
488
+ if (!this._inFlightRefresh) {
489
+ // Keep .finally last. The stored promise must be the promise
490
+ // that clears the slot. Then the slot is free before a waiter
491
+ // resumes.
492
+ this._inFlightRefresh = this._adoptOrRefresh().finally(() => {
493
+ this._inFlightRefresh = null;
494
+ });
495
+ }
496
+ return this._inFlightRefresh;
497
+ }
498
+
499
+ /**
500
+ * Adopts a newer stored credential, or refreshes. Both steps run inside
501
+ * the marker, because the marker must cover the whole time this instance
502
+ * holds the slot. The adoption lives here, not in refreshAuth(), so a
503
+ * module that overrides refreshAuth() still gets it.
504
+ *
505
+ * @returns {Promise<boolean>} True if the instance holds a usable token.
506
+ */
507
+ async _adoptOrRefresh() {
508
+ const refreshSucceeded = await refreshContext.run(
509
+ { requester: this },
510
+ async () => {
511
+ if (await this._adoptNewerCredential()) return true;
512
+ return this.refreshAuth();
513
+ }
514
+ );
515
+ if (refreshSucceeded) this._authGeneration++;
516
+ return refreshSucceeded;
517
+ }
518
+
519
+ /**
520
+ * Hook for requesters that hold a rotating stored credential. Return true
521
+ * when the instance adopted a newer stored credential and needs no
522
+ * refresh. A module backed by a vendor SDK overrides this, calls super,
523
+ * and then copies the adopted tokens into its SDK client.
524
+ *
525
+ * @returns {Promise<boolean>} True if the instance adopted a newer
526
+ * credential.
527
+ */
528
+ async _adoptNewerCredential() {
529
+ return false;
530
+ }
531
+
532
+ /** True while this instance runs its own refresh. */
533
+ _isInsideRefreshFlow() {
534
+ return refreshContext.getStore()?.requester === this;
535
+ }
536
+
537
+ async refreshAuth() {
538
+ throw new Error('refreshAuth not yet defined in child of Requester');
539
+ }
540
+ }
541
+
542
+ 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 };