@friggframework/core 2.0.0--canary.703.12b980b.0 → 2.0.0--canary.707.df80bfd.0

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 (308) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/CLAUDE.md +28 -0
  3. package/README.md +105 -0
  4. package/application/commands/integration-commands.js +20 -10
  5. package/application/commands/scheduler-commands.js +11 -7
  6. package/application/commands/user-commands.js +17 -5
  7. package/application/index.js +6 -2
  8. package/core/Worker.js +31 -7
  9. package/core/create-handler.js +9 -0
  10. package/credential/repositories/credential-repository-documentdb.js +34 -0
  11. package/credential/repositories/credential-repository-interface.js +15 -0
  12. package/credential/repositories/credential-repository-mongo.js +23 -0
  13. package/credential/repositories/credential-repository-postgres.js +28 -0
  14. package/credential/use-cases/credential-operations-for-user.js +202 -0
  15. package/credential/use-cases/reauthorize-credential-for-user.js +73 -0
  16. package/database/config.js +48 -24
  17. package/database/documentdb-encryption-service.js +9 -22
  18. package/database/documentdb-utils.js +2 -2
  19. package/database/encryption/README.md +78 -14
  20. package/database/encryption/documentdb-encryption-service.md +78 -1
  21. package/database/encryption/encryption-config.js +219 -0
  22. package/database/encryption/encryption-schema-registry.js +121 -24
  23. package/database/encryption/field-encryption-service.js +69 -5
  24. package/database/encryption/integration-mapping-encryption.js +1 -1
  25. package/database/encryption/test-support/fake-ciphertext.js +15 -0
  26. package/database/organization-where.js +18 -0
  27. package/database/prisma.js +15 -38
  28. package/database/unique-violation.js +25 -0
  29. package/database/use-cases/check-encryption-health-use-case.js +43 -52
  30. package/errors/index.js +0 -4
  31. package/extensions/bootstrap-runner.js +43 -0
  32. package/extensions/extension-loader.js +230 -0
  33. package/extensions/index.js +44 -0
  34. package/extensions/initialize-app.js +123 -0
  35. package/extensions/prisma-composer.js +270 -0
  36. package/extensions/route-mounter.js +103 -0
  37. package/extensions/schema-composer.js +188 -0
  38. package/generated/prisma-mongodb/edge.js +33 -42
  39. package/generated/prisma-mongodb/index-browser.js +30 -39
  40. package/generated/prisma-mongodb/index.d.ts +3187 -2005
  41. package/generated/prisma-mongodb/index.js +33 -42
  42. package/generated/prisma-mongodb/package.json +1 -1
  43. package/generated/prisma-mongodb/schema.prisma +86 -59
  44. package/generated/prisma-mongodb/wasm.js +33 -42
  45. package/generated/prisma-postgresql/edge.js +33 -42
  46. package/generated/prisma-postgresql/index-browser.js +30 -39
  47. package/generated/prisma-postgresql/index.d.ts +3380 -2177
  48. package/generated/prisma-postgresql/index.js +33 -42
  49. package/generated/prisma-postgresql/package.json +1 -1
  50. package/generated/prisma-postgresql/schema.prisma +81 -57
  51. package/generated/prisma-postgresql/wasm.js +33 -42
  52. package/handlers/WEBHOOKS.md +0 -29
  53. package/handlers/app-definition-loader.js +128 -19
  54. package/handlers/app-handler-helpers.js +27 -8
  55. package/handlers/backend-utils.js +31 -25
  56. package/handlers/integration-event-dispatcher.js +3 -7
  57. package/handlers/middleware/admin-auth.js +14 -1
  58. package/handlers/routers/auth.js +46 -10
  59. package/handlers/routers/health.js +13 -4
  60. package/handlers/routers/integration-defined-routers.js +144 -92
  61. package/handlers/routers/integration-webhook-routers.js +100 -50
  62. package/handlers/workers/integration-defined-workers.js +5 -2
  63. package/index.js +66 -16
  64. package/infrastructure/scheduler/index.js +19 -6
  65. package/infrastructure/scheduler/netlify-scheduler-adapter.js +216 -0
  66. package/infrastructure/scheduler/scheduled-job-repository.js +118 -0
  67. package/infrastructure/scheduler/scheduler-service-factory.js +55 -7
  68. package/integrations/index.js +1 -14
  69. package/integrations/integration-base.js +2 -163
  70. package/integrations/integration-router.js +132 -102
  71. package/integrations/repositories/integration-mapping-repository-documentdb.js +0 -50
  72. package/integrations/repositories/integration-mapping-repository-interface.js +0 -15
  73. package/integrations/repositories/integration-mapping-repository-mongo.js +0 -29
  74. package/integrations/repositories/integration-mapping-repository-postgres.js +0 -30
  75. package/integrations/repositories/integration-mapping-repository.js +0 -29
  76. package/integrations/repositories/integration-not-found.js +14 -0
  77. package/integrations/repositories/integration-repository-documentdb.js +37 -9
  78. package/integrations/repositories/integration-repository-interface.js +17 -4
  79. package/integrations/repositories/integration-repository-mongo.js +64 -96
  80. package/integrations/repositories/integration-repository-postgres.js +63 -81
  81. package/integrations/repositories/process-repository-mongo.js +1 -1
  82. package/integrations/schedule-config.js +80 -0
  83. package/integrations/tests/doubles/test-integration-repository.js +12 -1
  84. package/integrations/use-cases/create-integration.js +56 -31
  85. package/integrations/use-cases/delete-integration-for-user.js +4 -4
  86. package/integrations/use-cases/dispatch-ongoing-syncs.js +100 -0
  87. package/integrations/use-cases/get-integration-for-user.js +2 -4
  88. package/integrations/use-cases/get-integration-instance.js +4 -4
  89. package/integrations/use-cases/get-integrations-for-user.js +79 -39
  90. package/integrations/use-cases/get-possible-integrations.js +2 -7
  91. package/integrations/use-cases/index.js +7 -1
  92. package/integrations/use-cases/integration-operations-for-user.js +256 -0
  93. package/integrations/use-cases/resolve-integration-ownership.js +177 -0
  94. package/integrations/use-cases/update-integration.js +4 -4
  95. package/integrations/utils/map-integration-dto.js +43 -14
  96. package/management-api/create-management-api-router.js +49 -0
  97. package/management-api/error-response.js +116 -0
  98. package/management-api/management-api-config.js +151 -0
  99. package/management-api/meta-router.js +76 -0
  100. package/management-api/openapi/build-openapi.js +144 -0
  101. package/management-api/openapi/components.js +415 -0
  102. package/management-api/openapi/v1-operations.js +83 -0
  103. package/management-api/openapi/v2-operations.js +264 -0
  104. package/management-api/route-registry.js +411 -0
  105. package/management-api/test-support/http-client.js +39 -0
  106. package/management-api/use-cases/get-management-api-meta.js +57 -0
  107. package/management-api/v1-deprecation.js +120 -0
  108. package/management-api/v2/create-v2-router.js +85 -0
  109. package/management-api/v2/dependencies.js +281 -0
  110. package/management-api/v2/dto.js +143 -0
  111. package/management-api/v2/handlers/authorization.js +94 -0
  112. package/management-api/v2/handlers/credentials.js +33 -0
  113. package/management-api/v2/handlers/entities.js +77 -0
  114. package/management-api/v2/handlers/integrations.js +128 -0
  115. package/management-api/v2/handlers/proxy.js +34 -0
  116. package/management-api/v2/request.js +85 -0
  117. package/management-api/v2/test-support/fake-dependencies.js +103 -0
  118. package/modules/domain/authorization-session.js +144 -0
  119. package/modules/entity-scope.js +15 -0
  120. package/modules/entity-type-registry.js +311 -0
  121. package/modules/index.js +14 -0
  122. package/modules/module-factory.js +12 -0
  123. package/modules/module.js +53 -2
  124. package/modules/proxy/proxy-bases.js +169 -0
  125. package/modules/proxy/proxy-errors.js +31 -0
  126. package/modules/proxy/proxy-path.js +183 -0
  127. package/modules/proxy/proxy-policy.js +277 -0
  128. package/modules/repositories/authorization-session-repository-documentdb.js +112 -0
  129. package/modules/repositories/authorization-session-repository-factory.js +31 -0
  130. package/modules/repositories/authorization-session-repository-interface.js +47 -0
  131. package/modules/repositories/authorization-session-repository-prisma.js +87 -0
  132. package/modules/repositories/entity-not-found.js +18 -0
  133. package/modules/repositories/module-repository-documentdb.js +51 -6
  134. package/modules/repositories/module-repository-interface.js +19 -2
  135. package/modules/repositories/module-repository-mongo.js +67 -74
  136. package/modules/repositories/module-repository-postgres.js +66 -74
  137. package/modules/repositories/module-repository.js +2 -1
  138. package/modules/requester/proxy-request.js +245 -0
  139. package/modules/requester/requester.js +35 -5
  140. package/modules/scope-config.js +192 -0
  141. package/modules/scope-policy.js +112 -0
  142. package/modules/scope-resolution.js +288 -0
  143. package/modules/scope-visibility.js +247 -0
  144. package/modules/tests/doubles/in-memory-authorization-session-repository.js +45 -0
  145. package/modules/tests/doubles/ownership-matching.js +18 -0
  146. package/modules/use-cases/authorize-entity-for-user.js +61 -0
  147. package/modules/use-cases/create-entity-from-credential.js +113 -0
  148. package/modules/use-cases/entity-operations-for-user.js +162 -0
  149. package/modules/use-cases/entity-types.js +139 -0
  150. package/modules/use-cases/execute-entity-proxy-request.js +331 -0
  151. package/modules/use-cases/get-authorization-requirements.js +100 -0
  152. package/modules/use-cases/get-entities-for-user.js +26 -13
  153. package/modules/use-cases/get-entity-module-for-user.js +113 -0
  154. package/modules/use-cases/get-entity-options-by-id.js +10 -4
  155. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  156. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  157. package/modules/use-cases/get-module.js +10 -4
  158. package/modules/use-cases/manage-access.js +21 -0
  159. package/modules/use-cases/multi-step-authorization.js +329 -0
  160. package/modules/use-cases/process-authorization-callback.js +53 -19
  161. package/modules/use-cases/refresh-entity-options.js +10 -4
  162. package/modules/use-cases/test-module-auth.js +10 -4
  163. package/package.json +6 -7
  164. package/prisma-mongodb/schema.prisma +86 -59
  165. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +28 -0
  166. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +26 -0
  167. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +25 -0
  168. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +28 -0
  169. package/prisma-postgresql/schema.prisma +81 -57
  170. package/providers/index.js +23 -0
  171. package/providers/resolve-provider.js +93 -0
  172. package/providers/runtime-detection.js +124 -0
  173. package/queues/index.js +25 -0
  174. package/queues/providers/index.js +11 -0
  175. package/queues/providers/netlify-background-provider.js +150 -0
  176. package/queues/providers/qstash-queue-provider.js +219 -0
  177. package/queues/providers/sqs-queue-provider.js +70 -0
  178. package/queues/queue-client-interface.js +55 -0
  179. package/queues/queue-message-auth.js +193 -0
  180. package/queues/queue-provider-factory.js +101 -0
  181. package/queues/queue-provider.js +65 -0
  182. package/queues/queuer-util.js +2 -9
  183. package/queues/sqs-queue-client.js +81 -0
  184. package/types/index.d.ts +2 -0
  185. package/types/modules/index.d.ts +214 -0
  186. package/types/user/index.d.ts +134 -0
  187. package/user/adopter-jwt-config.js +177 -0
  188. package/user/adopter-jwt-errors.js +59 -0
  189. package/user/adopter-token-verifier.js +122 -0
  190. package/user/app-user-helpers.js +97 -0
  191. package/user/create-user-authentication.js +64 -0
  192. package/user/membership.js +51 -0
  193. package/user/repositories/organization-membership-record.js +19 -0
  194. package/user/repositories/organization-membership-repository-documentdb.js +106 -0
  195. package/user/repositories/organization-membership-repository-factory.js +42 -0
  196. package/user/repositories/organization-membership-repository-interface.js +57 -0
  197. package/user/repositories/organization-membership-repository-mongo.js +13 -0
  198. package/user/repositories/organization-membership-repository-postgres.js +29 -0
  199. package/user/repositories/organization-membership-repository-prisma.js +81 -0
  200. package/user/repositories/user-repository-documentdb.js +27 -2
  201. package/user/repositories/user-repository-interface.js +18 -0
  202. package/user/repositories/user-repository-mongo.js +24 -0
  203. package/user/repositories/user-repository-postgres.js +18 -0
  204. package/user/tests/doubles/adopter-jwt-fixtures.js +220 -0
  205. package/user/use-cases/authenticate-user.js +7 -1
  206. package/user/use-cases/ensure-organization-membership.js +92 -0
  207. package/user/use-cases/find-individual-users-by-organization.js +54 -0
  208. package/user/use-cases/get-user-from-adopter-jwt.js +95 -124
  209. package/user/use-cases/get-user-from-x-frigg-headers.js +83 -80
  210. package/user/user.js +64 -1
  211. package/errors/mapping-already-exists-error.js +0 -14
  212. package/handlers/workers/integration-triggers.js +0 -25
  213. package/infrastructure/scheduler/recurrence.js +0 -204
  214. package/integrations/define/DESIGN.md +0 -122
  215. package/integrations/define/config.js +0 -98
  216. package/integrations/define/define-app.js +0 -66
  217. package/integrations/define/define-extension.js +0 -154
  218. package/integrations/define/define-integration.js +0 -335
  219. package/integrations/define/examples/deal-alerts.js +0 -140
  220. package/integrations/define/extension-binding.js +0 -174
  221. package/integrations/define/index.js +0 -11
  222. package/integrations/define/json-schema-lite.js +0 -68
  223. package/integrations/define/subscriptions.js +0 -186
  224. package/integrations/define/tests/fixtures/hubspot.js +0 -109
  225. package/integrations/define/tests/fixtures/slack.js +0 -26
  226. package/integrations/define/use.js +0 -39
  227. package/integrations/definitions/README.md +0 -278
  228. package/integrations/definitions/canonical.js +0 -11
  229. package/integrations/definitions/capabilities/AUDIT.md +0 -480
  230. package/integrations/definitions/capabilities/acmeerp.capabilities.json +0 -633
  231. package/integrations/definitions/capabilities/asana.capabilities.json +0 -279
  232. package/integrations/definitions/capabilities/calendly.capabilities.json +0 -385
  233. package/integrations/definitions/capabilities/docusign.capabilities.json +0 -314
  234. package/integrations/definitions/capabilities/fathom.capabilities.json +0 -238
  235. package/integrations/definitions/capabilities/github.capabilities.json +0 -585
  236. package/integrations/definitions/capabilities/hubspot.capabilities.json +0 -822
  237. package/integrations/definitions/capabilities/jira.capabilities.json +0 -391
  238. package/integrations/definitions/capabilities/klaviyo.capabilities.json +0 -503
  239. package/integrations/definitions/capabilities/linear.capabilities.json +0 -378
  240. package/integrations/definitions/capabilities/netsuite.capabilities.json +0 -535
  241. package/integrations/definitions/capabilities/notion.capabilities.json +0 -208
  242. package/integrations/definitions/capabilities/phone.capabilities.json +0 -511
  243. package/integrations/definitions/capabilities/pipedrive.capabilities.json +0 -430
  244. package/integrations/definitions/capabilities/quickbooks.capabilities.json +0 -635
  245. package/integrations/definitions/capabilities/salesforce.capabilities.json +0 -568
  246. package/integrations/definitions/capabilities/shopify.capabilities.json +0 -1128
  247. package/integrations/definitions/capabilities/slack.capabilities.json +0 -373
  248. package/integrations/definitions/capabilities/stripe.capabilities.json +0 -630
  249. package/integrations/definitions/capabilities/webflow.capabilities.json +0 -196
  250. package/integrations/definitions/capabilities/zendesk.capabilities.json +0 -422
  251. package/integrations/definitions/compile.js +0 -80
  252. package/integrations/definitions/config-form.js +0 -137
  253. package/integrations/definitions/examples/acme-erp-quickbooks.definition.json +0 -467
  254. package/integrations/definitions/examples/asana-slack.definition.json +0 -219
  255. package/integrations/definitions/examples/calendly-salesforce.definition.json +0 -459
  256. package/integrations/definitions/examples/docusign-salesforce.definition.json +0 -305
  257. package/integrations/definitions/examples/fathom-hubspot-notion.definition.json +0 -575
  258. package/integrations/definitions/examples/github-linear.definition.json +0 -322
  259. package/integrations/definitions/examples/hubspot-slack.definition.json +0 -427
  260. package/integrations/definitions/examples/klaviyo-hubspot.definition.json +0 -336
  261. package/integrations/definitions/examples/phone-hubspot.definition.json +0 -633
  262. package/integrations/definitions/examples/pipedrive-hubspot.definition.json +0 -136
  263. package/integrations/definitions/examples/pipedrive-phone.definition.json +0 -340
  264. package/integrations/definitions/examples/records/phone-hubspot.definition-version.json +0 -652
  265. package/integrations/definitions/examples/records/phone-hubspot.integration.json +0 -86
  266. package/integrations/definitions/examples/records/phone-hubspot.mappings.json +0 -39
  267. package/integrations/definitions/examples/records/phone-hubspot.workflow-run.json +0 -62
  268. package/integrations/definitions/examples/shopify-klaviyo.definition.json +0 -488
  269. package/integrations/definitions/examples/shopify-netsuite.definition.json +0 -622
  270. package/integrations/definitions/examples/stripe-quickbooks.definition.json +0 -476
  271. package/integrations/definitions/examples/webflow-hubspot-slack.definition.json +0 -262
  272. package/integrations/definitions/examples/zendesk-jira.definition.json +0 -604
  273. package/integrations/definitions/expressions.js +0 -229
  274. package/integrations/definitions/index.js +0 -48
  275. package/integrations/definitions/integration-runtime.js +0 -779
  276. package/integrations/definitions/interpreter.js +0 -104
  277. package/integrations/definitions/manifests.js +0 -177
  278. package/integrations/definitions/renderers/common.js +0 -67
  279. package/integrations/definitions/renderers/html.js +0 -129
  280. package/integrations/definitions/renderers/hubspot.js +0 -59
  281. package/integrations/definitions/renderers/preview.js +0 -154
  282. package/integrations/definitions/renderers/slack.js +0 -146
  283. package/integrations/definitions/repositories/definition-version-repository.js +0 -67
  284. package/integrations/definitions/repositories/workflow-run-repository.js +0 -80
  285. package/integrations/definitions/router.js +0 -170
  286. package/integrations/definitions/runtime.js +0 -268
  287. package/integrations/definitions/schema/capability-manifest.v0.1.schema.json +0 -549
  288. package/integrations/definitions/schema/installation.v0.1.schema.json +0 -130
  289. package/integrations/definitions/schema/integration-definition.v0.1.schema.json +0 -909
  290. package/integrations/definitions/schema/records.md +0 -105
  291. package/integrations/definitions/schema-check.js +0 -58
  292. package/integrations/definitions/scripts/check-manifests.js +0 -220
  293. package/integrations/definitions/scripts/validate.js +0 -66
  294. package/integrations/definitions/steps.js +0 -123
  295. package/integrations/definitions/sync-engine.js +0 -297
  296. package/integrations/definitions/tests/helpers-fake-crm.js +0 -49
  297. package/integrations/definitions/transforms.js +0 -91
  298. package/integrations/definitions/use-cases/definition-use-cases.js +0 -245
  299. package/integrations/definitions/validate.js +0 -436
  300. package/integrations/definitions/versioning.js +0 -176
  301. package/integrations/definitions/webhooks.js +0 -119
  302. package/integrations/integration-types.js +0 -73
  303. package/integrations/triggers.js +0 -244
  304. package/integrations/use-cases/run-trigger-tick.js +0 -155
  305. package/integrations/use-cases/run-trigger.js +0 -77
  306. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +0 -66
  307. package/prisma-postgresql/migrations/20261019000000_workflow_run_waits/migration.sql +0 -7
  308. package/prisma-postgresql/migrations/20261019120000_definition_workspace/migration.sql +0 -5
@@ -1,4 +1,3 @@
1
- const { resolveIntegrationClass } = require('../integration-types');
2
1
  // Removed Integration wrapper - using IntegrationBase directly
3
2
 
4
3
  /**
@@ -37,9 +36,10 @@ class GetIntegrationInstance {
37
36
  );
38
37
  }
39
38
 
40
- const integrationClass = await resolveIntegrationClass(
41
- this.integrationClasses,
42
- integrationRecord.config.type
39
+ const integrationClass = this.integrationClasses.find(
40
+ (integrationClass) =>
41
+ integrationClass.Definition.name ===
42
+ integrationRecord.config.type
43
43
  );
44
44
 
45
45
  if (!integrationClass) {
@@ -1,11 +1,18 @@
1
- const { resolveIntegrationClass } = require('../integration-types');
2
1
  // Removed Integration wrapper - using IntegrationBase directly
3
2
  const {
4
3
  mapIntegrationClassToIntegrationDTO,
5
4
  } = require('../utils/map-integration-dto');
5
+ const {
6
+ describeRecord,
7
+ visibilityClauses,
8
+ matchesListFilters,
9
+ } = require('../../modules/scope-visibility');
10
+ const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
6
11
 
7
12
  /**
8
- * Use case for retrieving all integrations for a specific user.
13
+ * Use case for retrieving the integrations a caller may see: the ones they own
14
+ * or their active organization owns, created in that organization (ADR-056
15
+ * §3.6), each with `tenancy` ({scope, owner, organizationId, access}).
9
16
  * @class GetIntegrationsForUser
10
17
  */
11
18
  class GetIntegrationsForUser {
@@ -16,13 +23,16 @@ class GetIntegrationsForUser {
16
23
  * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
17
24
  * @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
18
25
  * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
26
+ * @param {Object} [params.scopePolicy] - from createScopePolicy
19
27
  */
20
28
  constructor({
21
29
  integrationRepository,
22
30
  integrationClasses,
23
31
  moduleFactory,
24
32
  moduleRepository,
33
+ scopePolicy,
25
34
  }) {
35
+ this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
26
36
  /**
27
37
  * @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
28
38
  */
@@ -33,56 +43,86 @@ class GetIntegrationsForUser {
33
43
  }
34
44
 
35
45
  /**
36
- * Executes the retrieval of all integrations for a user.
37
46
  * @async
38
- * @param {string} userId - ID of the user whose integrations to retrieve.
39
- * @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
47
+ * @param {Object} user - authenticated User
48
+ * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
49
+ * @returns {Promise<Object[]>} Array of integration DTOs.
40
50
  */
41
- async execute(userId) {
42
- const integrationRecords =
43
- await this.integrationRepository.findIntegrationsByUserId(userId);
51
+ async execute(user, filters = {}) {
52
+ const ctx = this.scopePolicy.accessContext(user);
53
+ const records = await this.integrationRepository.findIntegrationsByOwnership(
54
+ visibilityClauses(ctx, 'integration')
55
+ );
44
56
 
45
57
  const integrations = [];
58
+ for (const record of records) {
59
+ // The query is the filter; the same rule again means a repository
60
+ // that returns too much cannot widen the list.
61
+ const tenancy = describeRecord(record, ctx, 'integration');
62
+ if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
63
+ integrations.push({ ...(await this.toDto(record)), tenancy });
64
+ }
65
+ return integrations;
66
+ }
46
67
 
47
- for (const integrationRecord of integrationRecords) {
48
- const entities = await this.moduleRepository.findEntitiesByIds(
49
- integrationRecord.entitiesIds
50
- );
68
+ /** Hydrates one stored integration record into its DTO. */
69
+ async toDto(integrationRecord) {
70
+ const entities = await this.moduleRepository.findEntitiesByIds(
71
+ integrationRecord.entitiesIds
72
+ );
51
73
 
52
- const integrationClass = await resolveIntegrationClass(
53
- this.integrationClasses,
74
+ const integrationClass = this.integrationClasses.find(
75
+ (integrationClass) =>
76
+ integrationClass.Definition.name ===
54
77
  integrationRecord.config.type
78
+ );
79
+
80
+ const modules = [];
81
+ for (const entity of entities) {
82
+ const moduleInstance = await this.moduleFactory.getModuleInstance(
83
+ entity.id,
84
+ integrationRecord.userId
55
85
  );
86
+ modules.push(moduleInstance);
87
+ }
56
88
 
57
- const modules = [];
58
- for (const entity of entities) {
59
- const moduleInstance =
60
- await this.moduleFactory.getModuleInstance(
61
- entity.id,
62
- integrationRecord.userId
63
- );
64
- modules.push(moduleInstance);
65
- }
89
+ return mapIntegrationClassToIntegrationDTO({
90
+ id: integrationRecord.id,
91
+ userId: integrationRecord.userId,
92
+ entities: entities,
93
+ config: integrationRecord.config,
94
+ status: integrationRecord.status,
95
+ version: integrationRecord.version,
96
+ messages: integrationRecord.messages || { errors: [], warnings: [] },
97
+ modules,
98
+ options: integrationClass.getOptionDetails(),
99
+ });
100
+ }
101
+ }
66
102
 
67
- const integrationData = {
68
- id: integrationRecord.id,
69
- userId: integrationRecord.userId,
70
- entities: entities,
71
- config: integrationRecord.config,
72
- status: integrationRecord.status,
73
- version: integrationRecord.version,
74
- messages: integrationRecord.messages || { errors: [], warnings: [] },
75
- modules,
76
- options: integrationClass.getOptionDetails(),
77
- };
103
+ /**
104
+ * Every integration one user id owns, with no organization rules: the lookup
105
+ * behind the app-level `friggCommands.findIntegrationsByUserId`, which has a
106
+ * user id and no request.
107
+ */
108
+ class GetIntegrationsByUserId {
109
+ /** @param {ConstructorParameters<typeof GetIntegrationsForUser>[0]} params */
110
+ constructor(params) {
111
+ this.integrationRepository = params.integrationRepository;
112
+ this.hydrator = new GetIntegrationsForUser(params);
113
+ }
78
114
 
79
- integrations.push(
80
- mapIntegrationClassToIntegrationDTO(integrationData)
81
- );
115
+ /** @param {string} userId */
116
+ async execute(userId) {
117
+ const records = await this.integrationRepository.findIntegrationsByUserId(
118
+ userId
119
+ );
120
+ const integrations = [];
121
+ for (const record of records) {
122
+ integrations.push(await this.hydrator.toDto(record));
82
123
  }
83
-
84
124
  return integrations;
85
125
  }
86
126
  }
87
127
 
88
- module.exports = { GetIntegrationsForUser };
128
+ module.exports = { GetIntegrationsForUser, GetIntegrationsByUserId };
@@ -1,5 +1,3 @@
1
- const { listIntegrationClasses } = require('../integration-types');
2
-
3
1
  /**
4
2
  * Use case for retrieving all possible integration types that can be created.
5
3
  * @class GetPossibleIntegrations
@@ -19,11 +17,8 @@ class GetPossibleIntegrations {
19
17
  * @async
20
18
  * @returns {Promise<Object[]>} Array of integration option details for all available integration types.
21
19
  */
22
- async execute({ userId } = {}) {
23
- // Registered classes and the types providers offer (published definitions, and the ones this user's workspace
24
- // built), one list.
25
- const classes = await listIntegrationClasses(this.integrationClasses, userId ? { workspaceId: String(userId) } : undefined);
26
- return classes.map((integrationClass) =>
20
+ async execute() {
21
+ return this.integrationClasses.map((integrationClass) =>
27
22
  integrationClass.getOptionDetails()
28
23
  );
29
24
  }
@@ -1,4 +1,7 @@
1
- const { GetIntegrationsForUser } = require('./get-integrations-for-user');
1
+ const {
2
+ GetIntegrationsForUser,
3
+ GetIntegrationsByUserId,
4
+ } = require('./get-integrations-for-user');
2
5
  const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
3
6
  const { CreateIntegration } = require('./create-integration');
4
7
  const { GetIntegration } = require('./get-integration');
@@ -6,9 +9,11 @@ const { CreateProcess } = require('./create-process');
6
9
  const { UpdateProcessState } = require('./update-process-state');
7
10
  const { UpdateProcessMetrics } = require('./update-process-metrics');
8
11
  const { GetProcess } = require('./get-process');
12
+ const { DispatchOngoingSyncs } = require('./dispatch-ongoing-syncs');
9
13
 
10
14
  module.exports = {
11
15
  GetIntegrationsForUser,
16
+ GetIntegrationsByUserId,
12
17
  DeleteIntegrationForUser,
13
18
  CreateIntegration,
14
19
  GetIntegration,
@@ -16,4 +21,5 @@ module.exports = {
16
21
  UpdateProcessState,
17
22
  UpdateProcessMetrics,
18
23
  GetProcess,
24
+ DispatchOngoingSyncs,
19
25
  };
@@ -0,0 +1,256 @@
1
+ const Boom = require('@hapi/boom');
2
+ const { describeRecord } = require('../../modules/scope-visibility');
3
+ const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
4
+ const { assertCanManage } = require('../../modules/use-cases/manage-access');
5
+ const { isIntegrationNotFound } = require('../repositories/integration-not-found');
6
+
7
+ /**
8
+ * Management API v2 integration operations. They put an access check
9
+ * (missing or unavailable integration = 404) in front of the existing
10
+ * integration use cases, and pass the record's owner id on. Access follows
11
+ * the owner and the organization the integration was created in (ADR-056
12
+ * §3.6), not the owner alone.
13
+ */
14
+
15
+ const notFound = (integrationId) =>
16
+ Boom.notFound(`Integration ${integrationId} not found`, {
17
+ code: 'INTEGRATION_NOT_FOUND',
18
+ });
19
+
20
+ /**
21
+ * The integration record, or 404 when it is missing or the caller may not
22
+ * use it: not theirs or their active organization's, or created in another
23
+ * organization.
24
+ */
25
+ class GetOwnedIntegrationRecord {
26
+ constructor({ integrationRepository, scopePolicy }) {
27
+ this.integrationRepository = integrationRepository;
28
+ this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
29
+ }
30
+
31
+ /** `{ scope, owner, organizationId, access }` of a record to this caller, or null. */
32
+ describe(record, user) {
33
+ return describeRecord(record, this.scopePolicy.accessContext(user), 'integration');
34
+ }
35
+
36
+ /**
37
+ * @param {string} integrationId
38
+ * @param {Object} user - authenticated User
39
+ * @param {{manage?: boolean}} [options] - `manage: true` for changing or
40
+ * deleting: a person may use an organization's integration but only an
41
+ * admin may change it (403 ORG_ADMIN_REQUIRED)
42
+ */
43
+ async execute(integrationId, user, { manage = false } = {}) {
44
+ let record = null;
45
+ try {
46
+ record = await this.integrationRepository.findIntegrationById(
47
+ integrationId
48
+ );
49
+ } catch (error) {
50
+ if (
51
+ /Invalid ID|ObjectId|Malformed/i.test(error?.message || '') ||
52
+ isIntegrationNotFound(error)
53
+ ) {
54
+ throw notFound(integrationId);
55
+ }
56
+ throw error;
57
+ }
58
+ const tenancy = record && this.describe(record, user);
59
+ if (!tenancy) throw notFound(integrationId);
60
+ if (manage) assertCanManage(tenancy.access, `integration ${integrationId}`);
61
+ return record;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Creates (or reuses) an integration between entities the caller may use.
67
+ * The entities' access, the binding scopes and the integration type are
68
+ * checked before anything is written, and the integration is owned and
69
+ * paired with an organization by `resolveIntegrationOwnership` (§3, §3.6).
70
+ */
71
+ class CreateIntegrationForUser {
72
+ constructor({ createIntegration, resolveIntegrationOwnership, integrationClasses }) {
73
+ this.createIntegration = createIntegration;
74
+ this.resolveIntegrationOwnership = resolveIntegrationOwnership;
75
+ this.integrationClasses = integrationClasses || [];
76
+ }
77
+
78
+ async execute({ entities, config }, user) {
79
+ if (!config || typeof config !== 'object' || typeof config.type !== 'string') {
80
+ throw Boom.badRequest('config.type is required', {
81
+ code: 'VALIDATION_ERROR',
82
+ details: { field: 'config.type' },
83
+ });
84
+ }
85
+ const known = this.integrationClasses.some(
86
+ (IntegrationClass) => IntegrationClass.Definition?.name === config.type
87
+ );
88
+ if (!known) {
89
+ throw Boom.badRequest(`Unknown integration type '${config.type}'`, {
90
+ code: 'UNKNOWN_INTEGRATION_TYPE',
91
+ });
92
+ }
93
+ if (
94
+ !Array.isArray(entities) ||
95
+ entities.some((id) => typeof id !== 'string' && typeof id !== 'number')
96
+ ) {
97
+ throw Boom.badRequest('entities must be an array of entity ids', {
98
+ code: 'VALIDATION_ERROR',
99
+ details: { field: 'entities' },
100
+ });
101
+ }
102
+ const { ownerId, organizationId, adoptUnscoped } =
103
+ await this.resolveIntegrationOwnership.execute({
104
+ entityIds: entities,
105
+ config,
106
+ user,
107
+ });
108
+ return this.createIntegration.execute(
109
+ entities.map(String),
110
+ ownerId,
111
+ config,
112
+ { organizationId, adoptUnscoped }
113
+ );
114
+ }
115
+ }
116
+
117
+ /** The hydrated integration instance (modules loaded) for an owned integration. */
118
+ class GetIntegrationInstanceForUser {
119
+ constructor({ getOwnedIntegrationRecord, getIntegrationInstance }) {
120
+ this.getOwnedIntegrationRecord = getOwnedIntegrationRecord;
121
+ this.getIntegrationInstance = getIntegrationInstance;
122
+ }
123
+
124
+ async execute(integrationId, user) {
125
+ const record = await this.getOwnedIntegrationRecord.execute(
126
+ integrationId,
127
+ user
128
+ );
129
+ return this.getIntegrationInstance.execute(record.id, record.userId);
130
+ }
131
+ }
132
+
133
+ /** Replaces an owned integration's config. */
134
+ class UpdateIntegrationForUser {
135
+ constructor({ getOwnedIntegrationRecord, updateIntegration }) {
136
+ this.getOwnedIntegrationRecord = getOwnedIntegrationRecord;
137
+ this.updateIntegration = updateIntegration;
138
+ }
139
+
140
+ async execute(integrationId, user, config) {
141
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
142
+ throw Boom.badRequest('config must be an object', {
143
+ code: 'VALIDATION_ERROR',
144
+ details: { field: 'config' },
145
+ });
146
+ }
147
+ const record = await this.getOwnedIntegrationRecord.execute(
148
+ integrationId,
149
+ user,
150
+ { manage: true }
151
+ );
152
+ return this.updateIntegration.execute(record.id, record.userId, config);
153
+ }
154
+ }
155
+
156
+ /** Deletes an owned integration (runs its onDelete hooks). */
157
+ class RemoveIntegrationForUser {
158
+ constructor({ getOwnedIntegrationRecord, deleteIntegrationForUser }) {
159
+ this.getOwnedIntegrationRecord = getOwnedIntegrationRecord;
160
+ this.deleteIntegrationForUser = deleteIntegrationForUser;
161
+ }
162
+
163
+ async execute(integrationId, user) {
164
+ const record = await this.getOwnedIntegrationRecord.execute(
165
+ integrationId,
166
+ user,
167
+ { manage: true }
168
+ );
169
+ await this.deleteIntegrationForUser.execute(record.id, record.userId);
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Sends one of the framework's user-facing events (GET_CONFIG_OPTIONS,
175
+ * GET_USER_ACTIONS, ...) to an owned integration. Handlers pass fixed event
176
+ * names only; caller-chosen action ids go through RunIntegrationActionForUser.
177
+ */
178
+ class SendIntegrationEventForUser {
179
+ constructor({ getIntegrationInstanceForUser }) {
180
+ this.getIntegrationInstanceForUser = getIntegrationInstanceForUser;
181
+ }
182
+
183
+ async execute(integrationId, user, event, payload) {
184
+ const instance = await this.getIntegrationInstanceForUser.execute(
185
+ integrationId,
186
+ user
187
+ );
188
+ return instance.send(event, payload);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Runs a user action on an owned integration. Unlike v1, the action id must
194
+ * be one the integration lists as a user action (static USER_ACTION events
195
+ * or its dynamic user actions), so a caller cannot fire internal events such
196
+ * as lifecycle hooks.
197
+ */
198
+ class RunIntegrationActionForUser {
199
+ constructor({ getIntegrationInstanceForUser }) {
200
+ this.getIntegrationInstanceForUser = getIntegrationInstanceForUser;
201
+ }
202
+
203
+ async execute(integrationId, user, actionId, payload) {
204
+ const instance = await this.getIntegrationInstanceForUser.execute(
205
+ integrationId,
206
+ user
207
+ );
208
+ const actions =
209
+ typeof instance.loadUserActions === 'function'
210
+ ? await instance.loadUserActions()
211
+ : {};
212
+ if (!actions || !Object.prototype.hasOwnProperty.call(actions, actionId)) {
213
+ throw Boom.notFound(
214
+ `Integration ${integrationId} has no user action '${actionId}'`,
215
+ { code: 'ACTION_NOT_FOUND' }
216
+ );
217
+ }
218
+ return instance.send(actionId, payload);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Runs the integration's auth test and reconciles its status. Returns the
224
+ * errors recorded during the test.
225
+ */
226
+ class TestIntegrationAuthForUser {
227
+ constructor({ getIntegrationInstanceForUser }) {
228
+ this.getIntegrationInstanceForUser = getIntegrationInstanceForUser;
229
+ }
230
+
231
+ /** @returns {Promise<{ ok: boolean, errors: Array<Object> }>} */
232
+ async execute(integrationId, user) {
233
+ const instance = await this.getIntegrationInstanceForUser.execute(
234
+ integrationId,
235
+ user
236
+ );
237
+ const start = Date.now();
238
+ const authPassed = await instance.testAuth();
239
+ await instance.reconcileAuthStatus(authPassed);
240
+ const errors = (instance.record?.messages?.errors || []).filter(
241
+ ({ timestamp }) => timestamp >= start
242
+ );
243
+ return { ok: Boolean(authPassed) && errors.length === 0, errors };
244
+ }
245
+ }
246
+
247
+ module.exports = {
248
+ GetOwnedIntegrationRecord,
249
+ CreateIntegrationForUser,
250
+ GetIntegrationInstanceForUser,
251
+ UpdateIntegrationForUser,
252
+ RemoveIntegrationForUser,
253
+ SendIntegrationEventForUser,
254
+ RunIntegrationActionForUser,
255
+ TestIntegrationAuthForUser,
256
+ };
@@ -0,0 +1,177 @@
1
+ const Boom = require('@hapi/boom');
2
+ const { SCOPES, resolveScope } = require('../../modules/scope-resolution');
3
+ const {
4
+ integrationOwnerKind,
5
+ resolveIntegrationOwner,
6
+ } = require('../../modules/scope-resolution');
7
+ const {
8
+ describeRecord,
9
+ unscopedRowsBelongHere,
10
+ } = require('../../modules/scope-visibility');
11
+ const {
12
+ bindingScopeSetting,
13
+ readIntegrationOwnership,
14
+ } = require('../../modules/scope-config');
15
+ const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
16
+ const { entityTypeNameOf } = require('../../modules/entity-type-registry');
17
+
18
+ const notFound = (entityId) =>
19
+ Boom.notFound(`Entity ${entityId} not found`, { code: 'ENTITY_NOT_FOUND' });
20
+
21
+ /**
22
+ * Who owns an integration about to be created, and in which organization
23
+ * (ADR-056 §3, §3.3, §3.6). Shared by the v1 and v2 create paths. Nothing is
24
+ * persisted before this has answered, so a refusal leaves no row behind.
25
+ *
26
+ * Every entity must be one the caller may use in the active organization. A
27
+ * missing one, another organization's and a `global` one are the same 404, so
28
+ * ids cannot be probed. When the integration declares a scope (a binding's
29
+ * `scope` or `Definition.entities[key].scope`, or `Definition.ownership.scope`)
30
+ * the entity's scope must be in it. Levels below the integration (entity type,
31
+ * app, default) decide where a connection is CREATED and do not restrict what
32
+ * an integration may bind, so an app that adds a registry scope later does not
33
+ * break integrations that already use older connections.
34
+ *
35
+ * The owner follows the entities (all organization: the organization;
36
+ * otherwise the person) unless `Definition.ownership.owner` says. Every entity
37
+ * must then be of the owner's own scope: an integration is loaded by its owner,
38
+ * who must own what it uses, so an integration that mixes an organization
39
+ * entity with a person's own, or that names an owner that does not match, is
40
+ * refused with a 400 here, before anything is written.
41
+ */
42
+ class ResolveIntegrationOwnership {
43
+ /**
44
+ * @param {Object} params
45
+ * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
46
+ * @param {Array<Function>} params.integrationClasses
47
+ * @param {Object} [params.scopePolicy] - from createScopePolicy
48
+ */
49
+ constructor({ moduleRepository, integrationClasses, scopePolicy }) {
50
+ this.moduleRepository = moduleRepository;
51
+ this.integrationClasses = integrationClasses || [];
52
+ this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
53
+ }
54
+
55
+ /**
56
+ * @param {Object} params
57
+ * @param {Array<string|number>} params.entityIds
58
+ * @param {Object} params.config - `{ type }`
59
+ * @param {Object} params.user - authenticated User
60
+ * @returns {Promise<{ownerId: string, organizationId: (string|null), ownerKind: string, adoptUnscoped: boolean}>}
61
+ */
62
+ async execute({ entityIds, config, user }) {
63
+ const ctx = this.scopePolicy.accessContext(user);
64
+ const integrationClass = this.integrationClasses.find(
65
+ (IntegrationClass) => IntegrationClass.Definition?.name === config?.type
66
+ );
67
+ const ownership = readIntegrationOwnership(integrationClass);
68
+
69
+ const entities = await this.loadEntities(entityIds);
70
+ const scoped = entities.map((entity) => ({
71
+ entity,
72
+ scope: this.acceptedScope({ entity, ctx, integrationClass, ownership }),
73
+ }));
74
+
75
+ const ownerKind = integrationOwnerKind(
76
+ scoped.map(({ scope }) => scope),
77
+ ownership.owner
78
+ );
79
+ this.assertOneScope(scoped, ownerKind, config?.type);
80
+ return {
81
+ ...resolveIntegrationOwner(ownerKind, ctx),
82
+ ownerKind,
83
+ // Whether an existing integration with no organization (written
84
+ // before scoping) is this caller's to reuse. An organization-owned
85
+ // one is unambiguous: its owner is the organization, whoever asks
86
+ // and whatever their own legacy pointer says. A person's is theirs
87
+ // only where scope-visibility says (their legacy organization).
88
+ adoptUnscoped:
89
+ ownerKind === SCOPES.ORGANIZATION ||
90
+ unscopedRowsBelongHere(ctx, 'integration'),
91
+ };
92
+ }
93
+
94
+ /** Every entity must be of the integration owner's own scope (see the class comment). */
95
+ assertOneScope(scoped, ownerKind, type) {
96
+ for (const { entity, scope } of scoped) {
97
+ if (scope === ownerKind) continue;
98
+ // The load-time check (resolveOwnershipConfig) only sees declared
99
+ // scopes; this one sees the entities actually named.
100
+ if (ownerKind === SCOPES.ORGANIZATION) {
101
+ throw Boom.badRequest(
102
+ "An organization-owned integration cannot use an 'individual' entity: a shared integration must not run on one person's credential",
103
+ {
104
+ code: 'ORG_INTEGRATION_INDIVIDUAL_ENTITY',
105
+ details: { entityId: String(entity.id) },
106
+ }
107
+ );
108
+ }
109
+ throw Boom.badRequest(
110
+ `Integration '${type}' would be owned by the person but uses the organization entity ${entity.id}. An integration must use entities of one scope: all of the person's, or all of the organization's.`,
111
+ {
112
+ code: 'MIXED_ENTITY_SCOPES',
113
+ details: { entityId: String(entity.id), scope },
114
+ }
115
+ );
116
+ }
117
+ }
118
+
119
+ async loadEntities(entityIds) {
120
+ const ids = (entityIds || []).map(String);
121
+ if (ids.length === 0) return [];
122
+ let found;
123
+ try {
124
+ found = await this.moduleRepository.findEntitiesByIds(ids);
125
+ } catch (error) {
126
+ // A malformed id (e.g. not an integer on PostgreSQL) is "not found".
127
+ if (/Invalid ID|ObjectId|Malformed/i.test(error?.message || '')) {
128
+ throw notFound(ids[0]);
129
+ }
130
+ throw error;
131
+ }
132
+ const byId = new Map((found || []).filter(Boolean).map((e) => [String(e.id), e]));
133
+ return ids.map((id) => {
134
+ if (!byId.has(id)) throw notFound(id);
135
+ return byId.get(id);
136
+ });
137
+ }
138
+
139
+ /** The entity's scope for this caller, or a refusal. */
140
+ acceptedScope({ entity, ctx, integrationClass, ownership }) {
141
+ const described = describeRecord(entity, ctx, 'entity');
142
+ if (!described) throw notFound(entity.id);
143
+ const { scope } = described;
144
+
145
+ const declared = this.declaredScope({ entity, integrationClass, ownership });
146
+ if (declared && !declared.allow.includes(scope)) {
147
+ throw Boom.badRequest(
148
+ `Entity ${entity.id} is ${scope}-scoped, but this integration binds ${entity.moduleName} at ${declared.allow.join(' or ')} scope`,
149
+ {
150
+ code: 'ENTITY_SCOPE_MISMATCH',
151
+ details: { entityId: String(entity.id), scope, allowed: declared.allow },
152
+ }
153
+ );
154
+ }
155
+ return scope;
156
+ }
157
+
158
+ /**
159
+ * What the integration itself declares for this entity's binding (levels 2
160
+ * and 3 only), or null when it declares nothing.
161
+ */
162
+ declaredScope({ entity, integrationClass, ownership }) {
163
+ const Definition = integrationClass?.Definition;
164
+ const key = Object.keys(Definition?.modules || {}).find(
165
+ (candidate) =>
166
+ entityTypeNameOf(Definition.modules[candidate]?.definition) ===
167
+ entity.moduleName
168
+ );
169
+ const resolved = resolveScope({
170
+ binding: key === undefined ? undefined : bindingScopeSetting(Definition, key),
171
+ integration: ownership.scope,
172
+ });
173
+ return resolved.decidedBy === 'default' ? null : resolved;
174
+ }
175
+ }
176
+
177
+ module.exports = { ResolveIntegrationOwnership };
@@ -1,4 +1,3 @@
1
- const { resolveIntegrationClass } = require('../integration-types');
2
1
  const {
3
2
  mapIntegrationClassToIntegrationDTO,
4
3
  } = require('../utils/map-integration-dto');
@@ -42,9 +41,10 @@ class UpdateIntegration {
42
41
  }
43
42
 
44
43
  // 2. Get the correct Integration class by type
45
- const integrationClass = await resolveIntegrationClass(
46
- this.integrationClasses,
47
- integrationRecord.config.type
44
+ const integrationClass = this.integrationClasses.find(
45
+ (integrationClass) =>
46
+ integrationClass.Definition.name ===
47
+ integrationRecord.config.type
48
48
  );
49
49
 
50
50
  if (!integrationClass) {