@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
@@ -0,0 +1,605 @@
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
+ const { getLogger } = require('../../logs');
8
+ const { redactUrl } = require('../../logs/redact');
9
+ const { toSanitizedSurrogate } = require('../../logs/serialize');
10
+ const { getLoggerScope } = require('../../logs/context');
11
+
12
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
13
+
14
+ // A node-fetch error message holds the raw URL, and util.inspect prints the
15
+ // cause chain, so the FetchError keeps only a sanitized copy.
16
+ function sanitizedCause(err) {
17
+ const surrogate = toSanitizedSurrogate(err);
18
+ for (const key of ['errno', 'type']) {
19
+ if (typeof err?.[key] === 'string') surrogate[key] = err[key];
20
+ }
21
+ return surrogate;
22
+ }
23
+ const MAX_AUTH_RETRIES = 3;
24
+
25
+ // This context marks the async call chain that holds the refresh slot, and
26
+ // names the requester that holds it. Token requests re-enter _rawRequest
27
+ // through this._post. A 401 from inside that chain must fail fast: if it
28
+ // joins the refresh in flight, it awaits its own promise and hangs. The
29
+ // identity keeps a second requester used inside the chain out of the check.
30
+ // AsyncLocalStorage reaches the nested calls without help from the subclasses.
31
+ const refreshContext = new AsyncLocalStorage();
32
+
33
+ class Requester extends Delegate {
34
+ constructor(params) {
35
+ super(params);
36
+ this.backOff = get(params, 'backOff', [1, 3, 10, 30, 60, 180]);
37
+ this.isRefreshable = false;
38
+ this.refreshCount = 0;
39
+ this.authGraceRetryCount = 0;
40
+ // Concurrent 401s share one refreshAuth() run. See _refreshAuthOnce().
41
+ this._inFlightRefresh = null;
42
+ // This counter increases when the tokens change. A stale 401 (the
43
+ // token changed already) then retries and does not refresh again.
44
+ this._authGeneration = 0;
45
+ this.DLGT_INVALID_AUTH = 'INVALID_AUTH';
46
+ this.delegateTypes.push(this.DLGT_INVALID_AUTH);
47
+ this.agent = get(params, 'agent', null);
48
+
49
+ // Per-attempt HTTP timeout. Without this the framework called fetch()
50
+ // with no AbortController and no timeout — a silently-hung TCP
51
+ // connection (server accepts but never responds) blocked the calling
52
+ // promise forever, cascading into stalled batches, stalled syncs,
53
+ // and worker-lambda timeouts.
54
+ //
55
+ // Configuration precedence:
56
+ // 1. Instance param: new Requester({ requestTimeoutMs: 30_000 })
57
+ // 2. Class static: static requestTimeoutMs = 30_000
58
+ // 3. Default: DEFAULT_REQUEST_TIMEOUT_MS (60s)
59
+ //
60
+ // Pass 0 (or null) to disable the timeout entirely — reserved for
61
+ // test doubles and documented long-running endpoints.
62
+ // Intentionally NOT using `get(params, ...)` here — the Frigg
63
+ // `get` helper throws RequiredPropertyError if the key is missing
64
+ // and no default is provided, which would collide with the fall-
65
+ // through to the class-level static override.
66
+ const instanceTimeout = params?.requestTimeoutMs;
67
+ this.requestTimeoutMs =
68
+ instanceTimeout !== undefined && instanceTimeout !== null
69
+ ? instanceTimeout
70
+ : this.constructor.requestTimeoutMs ??
71
+ DEFAULT_REQUEST_TIMEOUT_MS;
72
+
73
+ // Allow passing in the fetch function
74
+ // Instance methods can use this.fetch without differentiating
75
+ this.fetch = get(params, 'fetch', fetch);
76
+
77
+ // Defaults to the process singleton. Pass an integration's bound
78
+ // `this.telemetry` to attribute out-of-band requests — setup/OAuth calls
79
+ // made before an integration context exists aren't rolled up otherwise.
80
+ this.telemetry = (params && params.telemetry) || getTelemetry();
81
+
82
+ // Not `get(params, 'logger')`: it throws when the key is missing.
83
+ this._logger = params?.logger ?? null;
84
+ }
85
+
86
+ // Resolved per read, so a delegate set after construction names the logger.
87
+ get logger() {
88
+ return this._logger ?? getLogger(`module.${this._telemetryModuleLabel()}`);
89
+ }
90
+
91
+ set logger(logger) {
92
+ this._logger = logger ?? null;
93
+ }
94
+
95
+ /**
96
+ * Redact secrets/PII from a URL before it touches telemetry. Many API
97
+ * modules embed credentials in the query string (?api_key=, ?token=,
98
+ * presigned signatures) or in userinfo — those must never reach a span,
99
+ * the bus, or an exporter. Keep only protocol + host + path (enough for
100
+ * North Star endpoint matching).
101
+ */
102
+ _sanitizeUrl(url) {
103
+ const raw = String(url);
104
+ try {
105
+ const u = new URL(raw);
106
+ return `${u.protocol}//${u.host}${u.pathname}`;
107
+ } catch (_) {
108
+ // Relative/opaque URL: drop the query string at minimum.
109
+ return raw.split('?')[0];
110
+ }
111
+ }
112
+
113
+ /** Bounded module label for the apimodule.requests metric. */
114
+ _telemetryModuleLabel() {
115
+ return (
116
+ this.moduleName ||
117
+ this.delegate?.name ||
118
+ this.delegate?.constructor?.name ||
119
+ this.constructor?.name ||
120
+ 'unknown'
121
+ );
122
+ }
123
+
124
+ parsedBody = async (resp) => {
125
+ const contentType = resp.headers.get('Content-Type') || '';
126
+
127
+ if (
128
+ contentType.match(/^application\/json/) ||
129
+ contentType.match(/^application\/vnd.api\+json/) ||
130
+ contentType.match(/^application\/hal\+json/)
131
+ ) {
132
+ return resp.json();
133
+ }
134
+
135
+ return resp.text();
136
+ };
137
+
138
+ /**
139
+ * Instrumenting entry point. Wraps the whole logical request —
140
+ * including retry/refresh recursion — in a single span + one
141
+ * `frigg.apimodule.requests` counter, emitted on the `attempt === 0`
142
+ * boundary so retries are never double-counted. The full URL rides the
143
+ * span only; the metric carries bounded labels {module, method, status}
144
+ * — never endpoint.
145
+ *
146
+ * @param {string} url - The request URL, relative or absolute.
147
+ * @param {Object} options - Fetch options (method, headers, body, query,
148
+ * returnFullRes, etc.) built by the `_get`/`_post`/`_patch`/`_put`/
149
+ * `_delete` wrappers.
150
+ * @param {number} attempt - 0-based count of retries already made for
151
+ * this call. Non-zero only if a caller re-enters directly; normal
152
+ * retries recurse through `_rawRequest` instead (see below).
153
+ */
154
+ async _request(url, options = {}, attempt = 0) {
155
+ if (attempt !== 0) {
156
+ return this._rawRequest(url, options, attempt);
157
+ }
158
+
159
+ const telemetry = this.telemetry;
160
+ if (!telemetry || typeof telemetry.span !== 'function') {
161
+ return this._rawRequest(url, options, 0);
162
+ }
163
+
164
+ const module = this._telemetryModuleLabel();
165
+ const method = (options.method || 'GET').toUpperCase();
166
+ const safeUrl = this._sanitizeUrl(url);
167
+
168
+ return telemetry.span('frigg.apimodule.request', async (span) => {
169
+ if (span && typeof span.setAttributes === 'function') {
170
+ const { requestId, messageId } = getLoggerScope();
171
+ span.setAttributes({
172
+ 'frigg.module': module,
173
+ 'http.request.method': method,
174
+ // Redacted (no query/userinfo) — never emit raw URLs.
175
+ 'url.path': safeUrl,
176
+ ...(requestId !== undefined && { requestId }),
177
+ ...(messageId !== undefined && { messageId }),
178
+ });
179
+ }
180
+ // Redacted url (unbounded) rides the bus-only context for North Star
181
+ // derived-from-trace matching — never a metric label.
182
+ const busContext = { url: safeUrl };
183
+ try {
184
+ const result = await this._rawRequest(url, options, 0);
185
+ telemetry.count(
186
+ 'frigg.apimodule.requests',
187
+ 1,
188
+ { module, method, status: 'ok' },
189
+ busContext
190
+ );
191
+ return result;
192
+ } catch (err) {
193
+ const code = err?.status ?? err?.statusCode;
194
+ const status = code ? String(code) : 'error';
195
+ if (span && typeof span.setAttribute === 'function' && code) {
196
+ span.setAttribute('http.response.status_code', code);
197
+ }
198
+ telemetry.count(
199
+ 'frigg.apimodule.requests',
200
+ 1,
201
+ { module, method, status },
202
+ busContext
203
+ );
204
+ throw err;
205
+ }
206
+ });
207
+ }
208
+
209
+ /**
210
+ * @param {string} url - The request URL, relative or absolute.
211
+ * @param {Object} options - Fetch options, as built by `_request`.
212
+ * @param {number} attempt - 0-based count of retries already made for
213
+ * this call. Indexes `this.backOff` for the next delay and is passed
214
+ * back in on each recursive retry.
215
+ */
216
+ async _rawRequest(url, options, attempt = 0) {
217
+ let encodedUrl = encodeURI(url);
218
+ if (options.query) {
219
+ let queryBuild = '?';
220
+ for (const key in options.query) {
221
+ queryBuild += `${encodeURIComponent(key)}=${encodeURIComponent(
222
+ options.query[key]
223
+ )}&`;
224
+ }
225
+ encodedUrl += queryBuild.slice(0, -1);
226
+ }
227
+
228
+ options.headers = await this.addAuthHeaders(options.headers);
229
+
230
+ // A 401 that arrives after a concurrent refresh is stale. It is not
231
+ // proof that the new token failed.
232
+ const authGenerationAtDispatch = this._authGeneration;
233
+
234
+ if (this.agent) options.agent = this.agent;
235
+
236
+ // Per-attempt timeout — fresh AbortController per call so the retry
237
+ // recursion (with its own backoff sleeps) always gets a clean
238
+ // signal. Timer is cleared in the finally block regardless of
239
+ // outcome.
240
+ const timeoutMs = this.requestTimeoutMs;
241
+ const controller = timeoutMs > 0 ? new AbortController() : null;
242
+ const timeoutHandle = controller
243
+ ? setTimeout(() => controller.abort(), timeoutMs)
244
+ : null;
245
+ const fetchOptions = controller
246
+ ? { ...options, signal: controller.signal }
247
+ : options;
248
+
249
+ // Timer must stay active through body consumption. node-fetch v2
250
+ // resolves the fetch() promise when headers arrive, not when the
251
+ // body is fully read — so a server that sends headers and then
252
+ // stalls the body would still hang parsedBody() or
253
+ // FetchError.create()'s response.text() call. We clear the timer
254
+ // only after the body is fully consumed (success path) or
255
+ // deliberately before each recursive retry so the new attempt
256
+ // starts with its own fresh timer.
257
+ let timerCleared = false;
258
+ const clearRequestTimer = () => {
259
+ if (!timerCleared && timeoutHandle) {
260
+ clearTimeout(timeoutHandle);
261
+ timerCleared = true;
262
+ }
263
+ };
264
+
265
+ try {
266
+ let response;
267
+ try {
268
+ response = await this.fetch(encodedUrl, fetchOptions);
269
+ } catch (e) {
270
+ // AbortController fires AbortError (name) / ETIMEDOUT-shaped
271
+ // errors (type on node-fetch) when we hit the timeout. No
272
+ // retry on timeout: a slow endpoint is a downstream problem,
273
+ // and each retry would wait another `timeoutMs` before giving
274
+ // up — amplifying the hang into a per-record multi-minute
275
+ // stall at batch scale.
276
+ const isTimeout =
277
+ e?.name === 'AbortError' || e?.type === 'aborted';
278
+ if (e?.code === 'ECONNRESET' && attempt < this.backOff.length) {
279
+ clearRequestTimer();
280
+ const delay = this.backOff[attempt] * 1000;
281
+ await new Promise((resolve) => setTimeout(resolve, delay));
282
+ return this._rawRequest(url, options, attempt + 1);
283
+ }
284
+ const fetchError = await FetchError.create({
285
+ resource: encodedUrl,
286
+ init: options,
287
+ cause: sanitizedCause(e),
288
+ });
289
+ if (isTimeout) {
290
+ // Flag + machine-readable fields so callers can
291
+ // distinguish a timeout from a generic network error
292
+ // without parsing the message.
293
+ fetchError.isTimeout = true;
294
+ fetchError.timeoutMs = timeoutMs;
295
+ }
296
+ throw fetchError;
297
+ }
298
+
299
+ const { status } = response;
300
+
301
+ // If the status is retriable and there are back off requests left, retry the request
302
+ if (
303
+ (status === 429 || status >= 500) &&
304
+ attempt < this.backOff.length
305
+ ) {
306
+ clearRequestTimer();
307
+ const delay = this.backOff[attempt] * 1000;
308
+ await new Promise((resolve) => setTimeout(resolve, delay));
309
+ return this._rawRequest(url, options, attempt + 1);
310
+ }
311
+
312
+ if (status === 401) {
313
+ // A 401 from inside the refresh flow means the provider
314
+ // rejected the credential itself (invalid_client). A new
315
+ // refresh cannot help. A join would await this same call.
316
+ if (this._isInsideRefreshFlow()) {
317
+ throw await this._invalidateAuth(
318
+ encodedUrl,
319
+ options,
320
+ response
321
+ );
322
+ }
323
+
324
+ const tokenReplacedWhileInFlight =
325
+ this._authGeneration !== authGenerationAtDispatch;
326
+ if (this.isRefreshable && tokenReplacedWhileInFlight) {
327
+ // This request did not try the current token. Retry with
328
+ // it. Do not spend one more provider-side rotation.
329
+ clearRequestTimer();
330
+ return this._rawRequest(url, options, attempt + 1);
331
+ }
332
+
333
+ if (!this.isRefreshable) {
334
+ // Up to MAX_AUTH_RETRIES grace retries before invalidating
335
+ // — a 401 alone isn't proof the credential is bad.
336
+ if (
337
+ this.authGraceRetryCount < MAX_AUTH_RETRIES &&
338
+ this.authGraceRetryCount < this.backOff.length
339
+ ) {
340
+ const delay =
341
+ this.backOff[this.authGraceRetryCount] * 1000;
342
+ this.authGraceRetryCount++;
343
+ clearRequestTimer();
344
+ await new Promise((resolve) =>
345
+ setTimeout(resolve, delay)
346
+ );
347
+ return this._rawRequest(url, options, attempt + 1);
348
+ }
349
+
350
+ throw await this._invalidateAuth(
351
+ encodedUrl,
352
+ options,
353
+ response
354
+ );
355
+ }
356
+
357
+ // Concurrent 401s share one refresh. Independent refreshes
358
+ // kill each other, because many providers use single-use
359
+ // refresh tokens. Only the initiator spends the retry budget.
360
+ const refreshAlreadyInFlight = Boolean(this._inFlightRefresh);
361
+
362
+ if (
363
+ !refreshAlreadyInFlight &&
364
+ this.refreshCount >= MAX_AUTH_RETRIES
365
+ ) {
366
+ throw await this._invalidateAuth(
367
+ encodedUrl,
368
+ options,
369
+ response
370
+ );
371
+ }
372
+
373
+ if (!refreshAlreadyInFlight) {
374
+ this.refreshCount++;
375
+ }
376
+
377
+ const refreshSucceeded = await this._refreshAuthOnce();
378
+ if (refreshSucceeded) {
379
+ clearRequestTimer();
380
+ return this._rawRequest(url, options, attempt + 1);
381
+ }
382
+
383
+ throw await this._invalidateAuth(encodedUrl, options, response);
384
+ }
385
+
386
+ // If the error wasn't retried, throw. FetchError.create reads
387
+ // the response body (response.text()) — timer must still be
388
+ // alive to catch a stalled body stream.
389
+ if (status >= 400) {
390
+ this._logRequestFailed(encodedUrl, options, status);
391
+ const fetchError = await FetchError.create({
392
+ resource: encodedUrl,
393
+ init: options,
394
+ response,
395
+ });
396
+ throw this._maybeFlagTimeoutDuringBodyRead(
397
+ fetchError,
398
+ timeoutMs
399
+ );
400
+ }
401
+
402
+ // Successful response: reset the per-instance refresh budget so
403
+ // a later 401 in the same Requester lifetime can attempt refresh
404
+ // again instead of silently falling through.
405
+ this.refreshCount = 0;
406
+ this.authGraceRetryCount = 0;
407
+
408
+ // parsedBody consumes the response body stream. If the server
409
+ // stalls mid-stream the timer (still armed) aborts it.
410
+ return options.returnFullRes
411
+ ? response
412
+ : await this.parsedBody(response);
413
+ } catch (e) {
414
+ // If the abort fired during body consumption, node-fetch emits
415
+ // the error as an AbortError on the body stream. Surface the
416
+ // same isTimeout flag callers use for header-phase timeouts.
417
+ const flagged = this._maybeFlagTimeoutDuringBodyRead(e, timeoutMs);
418
+ throw this._wrapFetchLibraryError(flagged, encodedUrl, options);
419
+ } finally {
420
+ clearRequestTimer();
421
+ }
422
+ }
423
+
424
+ _logRequestFailed(encodedUrl, options, status) {
425
+ const logger = this.logger;
426
+ if (!logger.isLevelEnabled('DEBUG')) return;
427
+ logger.debug('Request failed', {
428
+ eventName: `${logger.name}.request_failed`,
429
+ method: (options.method || 'GET').toUpperCase(),
430
+ url: redactUrl(encodedUrl),
431
+ statusCode: status,
432
+ headerNames: Object.keys(options.headers || {}),
433
+ });
434
+ }
435
+
436
+ async _invalidateAuth(encodedUrl, options, response) {
437
+ const fetchError = await FetchError.create({
438
+ resource: encodedUrl,
439
+ init: options,
440
+ response,
441
+ });
442
+ await this.notify(this.DLGT_INVALID_AUTH, fetchError);
443
+ return fetchError;
444
+ }
445
+
446
+ // node-fetch names its own error class FetchError too, and its message
447
+ // holds the raw URL. Only our class passes through unwrapped.
448
+ _wrapFetchLibraryError(err, encodedUrl, options) {
449
+ if (!err || typeof err !== 'object' || err instanceof FetchError) {
450
+ return err;
451
+ }
452
+ const isFetchLibraryError =
453
+ err.name === 'FetchError' ||
454
+ err.name === 'AbortError' ||
455
+ typeof err.type === 'string';
456
+ if (!isFetchLibraryError) return err;
457
+ const wrapped = new FetchError({
458
+ resource: encodedUrl,
459
+ init: options,
460
+ cause: sanitizedCause(err),
461
+ });
462
+ if (err.isTimeout) {
463
+ wrapped.isTimeout = true;
464
+ wrapped.timeoutMs = err.timeoutMs;
465
+ }
466
+ return wrapped;
467
+ }
468
+
469
+ _maybeFlagTimeoutDuringBodyRead(err, timeoutMs) {
470
+ if (!err || typeof err !== 'object') return err;
471
+ if (err.isTimeout) return err;
472
+ const isAbort = err.name === 'AbortError' || err.type === 'aborted';
473
+ if (!isAbort) return err;
474
+ err.isTimeout = true;
475
+ err.timeoutMs = timeoutMs;
476
+ return err;
477
+ }
478
+
479
+ async _get(options) {
480
+ const fetchOptions = {
481
+ method: 'GET',
482
+ credentials: 'include',
483
+ headers: options.headers || {},
484
+ query: options.query || {},
485
+ returnFullRes: options.returnFullRes || false,
486
+ };
487
+
488
+ const res = await this._request(options.url, fetchOptions);
489
+ return res;
490
+ }
491
+
492
+ async _post(options, stringify = true) {
493
+ const fetchOptions = {
494
+ method: 'POST',
495
+ credentials: 'include',
496
+ headers: options.headers || {},
497
+ query: options.query || {},
498
+ body: stringify ? JSON.stringify(options.body) : options.body,
499
+ returnFullRes: options.returnFullRes || false,
500
+ };
501
+ const res = await this._request(options.url, fetchOptions);
502
+ return res;
503
+ }
504
+
505
+ async _patch(options, stringify = true) {
506
+ const fetchOptions = {
507
+ method: 'PATCH',
508
+ credentials: 'include',
509
+ headers: options.headers || {},
510
+ query: options.query || {},
511
+ body: stringify ? JSON.stringify(options.body) : options.body,
512
+ returnFullRes: options.returnFullRes || false,
513
+ };
514
+ const res = await this._request(options.url, fetchOptions);
515
+ return res;
516
+ }
517
+
518
+ async _put(options, stringify = true) {
519
+ const fetchOptions = {
520
+ method: 'PUT',
521
+ credentials: 'include',
522
+ headers: options.headers || {},
523
+ query: options.query || {},
524
+ body: stringify ? JSON.stringify(options.body) : options.body,
525
+ returnFullRes: options.returnFullRes || false,
526
+ };
527
+ const res = await this._request(options.url, fetchOptions);
528
+ return res;
529
+ }
530
+
531
+ async _delete(options) {
532
+ const fetchOptions = {
533
+ method: 'DELETE',
534
+ credentials: 'include',
535
+ headers: options.headers || {},
536
+ query: options.query || {},
537
+ returnFullRes: options.returnFullRes || true,
538
+ };
539
+ return this._request(options.url, fetchOptions);
540
+ }
541
+
542
+ /**
543
+ * Runs one refreshAuth() at a time. The first caller starts the refresh.
544
+ * Callers that arrive during the refresh await the same promise. The
545
+ * check-and-store step is synchronous. Thus two concurrent callers
546
+ * cannot both start a refresh.
547
+ *
548
+ * @returns {Promise<boolean>} True if the refresh succeeded.
549
+ */
550
+ _refreshAuthOnce() {
551
+ if (!this._inFlightRefresh) {
552
+ // Keep .finally last. The stored promise must be the promise
553
+ // that clears the slot. Then the slot is free before a waiter
554
+ // resumes.
555
+ this._inFlightRefresh = this._adoptOrRefresh().finally(() => {
556
+ this._inFlightRefresh = null;
557
+ });
558
+ }
559
+ return this._inFlightRefresh;
560
+ }
561
+
562
+ /**
563
+ * Adopts a newer stored credential, or refreshes. Both steps run inside
564
+ * the marker, because the marker must cover the whole time this instance
565
+ * holds the slot. The adoption lives here, not in refreshAuth(), so a
566
+ * module that overrides refreshAuth() still gets it.
567
+ *
568
+ * @returns {Promise<boolean>} True if the instance holds a usable token.
569
+ */
570
+ async _adoptOrRefresh() {
571
+ const refreshSucceeded = await refreshContext.run(
572
+ { requester: this },
573
+ async () => {
574
+ if (await this._adoptNewerCredential()) return true;
575
+ return this.refreshAuth();
576
+ }
577
+ );
578
+ if (refreshSucceeded) this._authGeneration++;
579
+ return refreshSucceeded;
580
+ }
581
+
582
+ /**
583
+ * Hook for requesters that hold a rotating stored credential. Return true
584
+ * when the instance adopted a newer stored credential and needs no
585
+ * refresh. A module backed by a vendor SDK overrides this, calls super,
586
+ * and then copies the adopted tokens into its SDK client.
587
+ *
588
+ * @returns {Promise<boolean>} True if the instance adopted a newer
589
+ * credential.
590
+ */
591
+ async _adoptNewerCredential() {
592
+ return false;
593
+ }
594
+
595
+ /** True while this instance runs its own refresh. */
596
+ _isInsideRefreshFlow() {
597
+ return refreshContext.getStore()?.requester === this;
598
+ }
599
+
600
+ async refreshAuth() {
601
+ throw new Error('refreshAuth not yet defined in child of Requester');
602
+ }
603
+ }
604
+
605
+ 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 };