@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.694.f66a64a.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 (267) hide show
  1. package/CLAUDE.md +0 -28
  2. package/README.md +0 -105
  3. package/application/commands/integration-commands.js +10 -20
  4. package/application/commands/scheduler-commands.js +7 -11
  5. package/application/commands/user-commands.js +5 -17
  6. package/application/index.js +2 -6
  7. package/core/Worker.js +7 -31
  8. package/core/create-handler.js +0 -9
  9. package/credential/repositories/credential-repository-documentdb.js +0 -34
  10. package/credential/repositories/credential-repository-interface.js +0 -15
  11. package/credential/repositories/credential-repository-mongo.js +0 -23
  12. package/credential/repositories/credential-repository-postgres.js +0 -28
  13. package/database/config.js +24 -48
  14. package/database/documentdb-utils.js +2 -2
  15. package/database/encryption/encryption-schema-registry.js +12 -93
  16. package/database/prisma.js +2 -4
  17. package/errors/index.js +4 -0
  18. package/errors/mapping-already-exists-error.js +14 -0
  19. package/generated/prisma-mongodb/edge.js +38 -33
  20. package/generated/prisma-mongodb/index-browser.js +35 -30
  21. package/generated/prisma-mongodb/index.d.ts +1880 -3191
  22. package/generated/prisma-mongodb/index.js +38 -33
  23. package/generated/prisma-mongodb/package.json +1 -1
  24. package/generated/prisma-mongodb/schema.prisma +50 -86
  25. package/generated/prisma-mongodb/wasm.js +38 -33
  26. package/generated/prisma-postgresql/edge.js +38 -33
  27. package/generated/prisma-postgresql/index-browser.js +35 -30
  28. package/generated/prisma-postgresql/index.d.ts +2050 -3390
  29. package/generated/prisma-postgresql/index.js +38 -33
  30. package/generated/prisma-postgresql/package.json +1 -1
  31. package/generated/prisma-postgresql/schema.prisma +48 -81
  32. package/generated/prisma-postgresql/wasm.js +38 -33
  33. package/handlers/WEBHOOKS.md +29 -0
  34. package/handlers/app-definition-loader.js +19 -128
  35. package/handlers/app-handler-helpers.js +3 -25
  36. package/handlers/backend-utils.js +25 -31
  37. package/handlers/middleware/admin-auth.js +1 -14
  38. package/handlers/routers/auth.js +10 -46
  39. package/handlers/routers/health.js +4 -13
  40. package/handlers/routers/integration-defined-routers.js +92 -144
  41. package/handlers/routers/integration-webhook-routers.js +64 -102
  42. package/handlers/workers/integration-defined-workers.js +2 -5
  43. package/handlers/workers/integration-triggers.js +25 -0
  44. package/index.js +3 -65
  45. package/infrastructure/scheduler/index.js +6 -19
  46. package/infrastructure/scheduler/recurrence.js +204 -0
  47. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  48. package/integrations/data-defined/README.md +185 -0
  49. package/integrations/data-defined/canonical.js +11 -0
  50. package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
  51. package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
  52. package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
  53. package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
  54. package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
  55. package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
  56. package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
  57. package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
  58. package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
  59. package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
  60. package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
  61. package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
  62. package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
  63. package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
  64. package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
  65. package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
  66. package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
  67. package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
  68. package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
  69. package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
  70. package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
  71. package/integrations/data-defined/compile.js +80 -0
  72. package/integrations/data-defined/config-form.js +137 -0
  73. package/integrations/data-defined/data-defined-integration.js +417 -0
  74. package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
  75. package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
  76. package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
  77. package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
  78. package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
  79. package/integrations/data-defined/examples/github-linear.definition.json +316 -0
  80. package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
  81. package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
  82. package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
  83. package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
  84. package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
  85. package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
  86. package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
  87. package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
  88. package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
  89. package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
  90. package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
  91. package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
  92. package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
  93. package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
  94. package/integrations/data-defined/expressions.js +225 -0
  95. package/integrations/data-defined/index.js +39 -0
  96. package/integrations/data-defined/interpreter.js +89 -0
  97. package/integrations/data-defined/manifests.js +125 -0
  98. package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
  99. package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
  100. package/integrations/data-defined/router.js +97 -0
  101. package/integrations/data-defined/runtime.js +208 -0
  102. package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
  103. package/integrations/data-defined/schema/installation.v0.1.schema.json +130 -0
  104. package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +902 -0
  105. package/integrations/data-defined/schema/records.md +98 -0
  106. package/integrations/data-defined/schema-check.js +58 -0
  107. package/integrations/data-defined/scripts/validate.js +66 -0
  108. package/integrations/data-defined/steps.js +89 -0
  109. package/integrations/data-defined/transforms.js +91 -0
  110. package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
  111. package/integrations/data-defined/validate.js +384 -0
  112. package/integrations/data-defined/versioning.js +176 -0
  113. package/integrations/data-defined/webhooks.js +119 -0
  114. package/integrations/index.js +0 -1
  115. package/integrations/integration-base.js +117 -0
  116. package/integrations/integration-router.js +101 -131
  117. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  118. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  119. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  120. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  121. package/integrations/repositories/integration-mapping-repository.js +29 -0
  122. package/integrations/repositories/integration-repository-documentdb.js +9 -37
  123. package/integrations/repositories/integration-repository-interface.js +4 -17
  124. package/integrations/repositories/integration-repository-mongo.js +96 -64
  125. package/integrations/repositories/integration-repository-postgres.js +81 -63
  126. package/integrations/repositories/process-repository-mongo.js +1 -1
  127. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  128. package/integrations/triggers.js +244 -0
  129. package/integrations/use-cases/create-integration.js +20 -55
  130. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  131. package/integrations/use-cases/index.js +1 -7
  132. package/integrations/use-cases/run-trigger-tick.js +132 -0
  133. package/integrations/use-cases/run-trigger.js +77 -0
  134. package/integrations/utils/map-integration-dto.js +14 -43
  135. package/modules/index.js +0 -14
  136. package/modules/module-factory.js +0 -12
  137. package/modules/module.js +2 -53
  138. package/modules/repositories/module-repository-documentdb.js +6 -51
  139. package/modules/repositories/module-repository-interface.js +2 -19
  140. package/modules/repositories/module-repository-mongo.js +74 -67
  141. package/modules/repositories/module-repository-postgres.js +74 -66
  142. package/modules/repositories/module-repository.js +1 -2
  143. package/modules/requester/requester.js +5 -35
  144. package/modules/use-cases/get-entities-for-user.js +13 -26
  145. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  146. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  147. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  148. package/modules/use-cases/get-module.js +4 -10
  149. package/modules/use-cases/process-authorization-callback.js +19 -53
  150. package/modules/use-cases/refresh-entity-options.js +4 -10
  151. package/modules/use-cases/test-module-auth.js +4 -10
  152. package/package.json +6 -6
  153. package/prisma-mongodb/schema.prisma +50 -86
  154. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
  155. package/prisma-postgresql/schema.prisma +48 -81
  156. package/queues/index.js +0 -25
  157. package/queues/queuer-util.js +9 -2
  158. package/types/index.d.ts +0 -2
  159. package/user/repositories/user-repository-documentdb.js +2 -27
  160. package/user/repositories/user-repository-interface.js +0 -18
  161. package/user/repositories/user-repository-mongo.js +0 -24
  162. package/user/repositories/user-repository-postgres.js +0 -18
  163. package/user/use-cases/authenticate-user.js +1 -7
  164. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  165. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  166. package/user/user.js +1 -64
  167. package/credential/use-cases/credential-operations-for-user.js +0 -202
  168. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  169. package/database/organization-where.js +0 -18
  170. package/database/unique-violation.js +0 -25
  171. package/extensions/bootstrap-runner.js +0 -43
  172. package/extensions/extension-loader.js +0 -187
  173. package/extensions/index.js +0 -41
  174. package/extensions/initialize-app.js +0 -123
  175. package/extensions/route-mounter.js +0 -103
  176. package/extensions/schema-composer.js +0 -188
  177. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  178. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  179. package/integrations/repositories/integration-not-found.js +0 -14
  180. package/integrations/schedule-config.js +0 -80
  181. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  182. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  183. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  184. package/management-api/create-management-api-router.js +0 -49
  185. package/management-api/error-response.js +0 -116
  186. package/management-api/management-api-config.js +0 -151
  187. package/management-api/meta-router.js +0 -76
  188. package/management-api/openapi/build-openapi.js +0 -144
  189. package/management-api/openapi/components.js +0 -415
  190. package/management-api/openapi/v1-operations.js +0 -83
  191. package/management-api/openapi/v2-operations.js +0 -264
  192. package/management-api/route-registry.js +0 -411
  193. package/management-api/test-support/http-client.js +0 -39
  194. package/management-api/use-cases/get-management-api-meta.js +0 -57
  195. package/management-api/v1-deprecation.js +0 -120
  196. package/management-api/v2/create-v2-router.js +0 -85
  197. package/management-api/v2/dependencies.js +0 -281
  198. package/management-api/v2/dto.js +0 -143
  199. package/management-api/v2/handlers/authorization.js +0 -94
  200. package/management-api/v2/handlers/credentials.js +0 -33
  201. package/management-api/v2/handlers/entities.js +0 -77
  202. package/management-api/v2/handlers/integrations.js +0 -128
  203. package/management-api/v2/handlers/proxy.js +0 -34
  204. package/management-api/v2/request.js +0 -85
  205. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  206. package/modules/domain/authorization-session.js +0 -144
  207. package/modules/entity-scope.js +0 -15
  208. package/modules/entity-type-registry.js +0 -311
  209. package/modules/proxy/proxy-bases.js +0 -169
  210. package/modules/proxy/proxy-errors.js +0 -31
  211. package/modules/proxy/proxy-path.js +0 -183
  212. package/modules/proxy/proxy-policy.js +0 -277
  213. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  214. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  215. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  216. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  217. package/modules/repositories/entity-not-found.js +0 -18
  218. package/modules/requester/proxy-request.js +0 -245
  219. package/modules/scope-config.js +0 -192
  220. package/modules/scope-policy.js +0 -112
  221. package/modules/scope-resolution.js +0 -288
  222. package/modules/scope-visibility.js +0 -247
  223. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  224. package/modules/tests/doubles/ownership-matching.js +0 -18
  225. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  226. package/modules/use-cases/create-entity-from-credential.js +0 -113
  227. package/modules/use-cases/entity-operations-for-user.js +0 -162
  228. package/modules/use-cases/entity-types.js +0 -139
  229. package/modules/use-cases/execute-entity-proxy-request.js +0 -331
  230. package/modules/use-cases/get-authorization-requirements.js +0 -100
  231. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  232. package/modules/use-cases/manage-access.js +0 -21
  233. package/modules/use-cases/multi-step-authorization.js +0 -329
  234. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  235. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  236. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  237. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  238. package/providers/index.js +0 -23
  239. package/providers/resolve-provider.js +0 -93
  240. package/providers/runtime-detection.js +0 -124
  241. package/queues/providers/index.js +0 -11
  242. package/queues/providers/netlify-background-provider.js +0 -150
  243. package/queues/providers/qstash-queue-provider.js +0 -219
  244. package/queues/providers/sqs-queue-provider.js +0 -70
  245. package/queues/queue-client-interface.js +0 -55
  246. package/queues/queue-message-auth.js +0 -193
  247. package/queues/queue-provider-factory.js +0 -101
  248. package/queues/queue-provider.js +0 -65
  249. package/queues/sqs-queue-client.js +0 -81
  250. package/types/modules/index.d.ts +0 -214
  251. package/types/user/index.d.ts +0 -134
  252. package/user/adopter-jwt-config.js +0 -177
  253. package/user/adopter-jwt-errors.js +0 -59
  254. package/user/adopter-token-verifier.js +0 -122
  255. package/user/app-user-helpers.js +0 -97
  256. package/user/create-user-authentication.js +0 -64
  257. package/user/membership.js +0 -51
  258. package/user/repositories/organization-membership-record.js +0 -19
  259. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  260. package/user/repositories/organization-membership-repository-factory.js +0 -42
  261. package/user/repositories/organization-membership-repository-interface.js +0 -57
  262. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  263. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  264. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  265. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  266. package/user/use-cases/ensure-organization-membership.js +0 -92
  267. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -120,7 +120,7 @@ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
120
120
  if (normalized.newState !== null) {
121
121
  $set.state = normalized.newState;
122
122
  }
123
- $set.updatedAt = new Date();
123
+ $set.updatedAt = { $date: new Date().toISOString() };
124
124
  update.$set = $set;
125
125
 
126
126
  if (Object.keys(normalized.pushSlice).length > 0) {
@@ -1,6 +1,5 @@
1
1
  const { v4: uuid } = require('uuid');
2
2
  const { validateConfigPatch } = require('../../repositories/config-patch-shared');
3
- const { matchesAnyClause } = require('../../../modules/tests/doubles/ownership-matching');
4
3
 
5
4
  class TestIntegrationRepository {
6
5
  constructor() {
@@ -8,14 +7,13 @@ class TestIntegrationRepository {
8
7
  this.operationHistory = [];
9
8
  }
10
9
 
11
- async createIntegration(entities, userId, config, tenancy = {}) {
10
+ async createIntegration(entities, userId, config) {
12
11
  const id = uuid();
13
12
  const record = {
14
13
  id,
15
14
  _id: id,
16
15
  entitiesIds: entities,
17
16
  userId: userId,
18
- organizationId: tenancy.organizationId ?? null,
19
17
  config,
20
18
  version: '0.0.0',
21
19
  status: 'IN_CREATION',
@@ -40,15 +38,6 @@ class TestIntegrationRepository {
40
38
  return results;
41
39
  }
42
40
 
43
- // Answers the way a database does for the clauses a use case hands it.
44
- async findIntegrationsByOwnership(clauses) {
45
- const results = Array.from(this.store.values()).filter((r) =>
46
- matchesAnyClause(r, clauses)
47
- );
48
- this.operationHistory.push({ operation: 'findByOwnership', clauses, count: results.length });
49
- return results;
50
- }
51
-
52
41
  async findIntegrations(filter = {}) {
53
42
  const { type, status } = filter;
54
43
  const results = Array.from(this.store.values()).filter((r) => {
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Recurring triggers (ADR-055): what an integration declares, how one installation resolves it, and whether it is
3
+ * due at a tick. Pure; no I/O. The tick itself is `use-cases/run-trigger-tick.js`; the run is
4
+ * `IntegrationBase.onTriggerRun`.
5
+ *
6
+ * static Definition = {
7
+ * name: 'housecallpro',
8
+ * triggers: {
9
+ * pollErp: { every: '15m', handler: 'pollErp' }, // every installation
10
+ * ongoingSync: { every: '1h', configurable: { min: '15m' }, handler: sync }, // an installation may retime it
11
+ * perPlan: { every: (config) => config.pollEvery, when: (config) => config.poll !== false, handler },
12
+ * digest: { cron: '0 8 * * 1-5', timezone: 'America/New_York', configurable: true, handler },
13
+ * nightly: { at: 'cron(0 3 * * ? *)', timezone: 'Europe/London', handler }, // same time for everyone
14
+ * },
15
+ * };
16
+ *
17
+ * Nothing is created per installation. Devtools provisions one function per integration with one schedule per
18
+ * distinct tick and one per `at` trigger (`provisioningOf`). At each tick the function pages through the ENABLED
19
+ * installations and sends `{ event: 'FRIGG_TRIGGER_RUN', data: { integrationId, trigger, scheduledFor } }` for each
20
+ * one that is due. A disabled or deleted installation is simply not due, and a config change applies at the next tick.
21
+ *
22
+ * Two additions to ADR-055 (its "Amendment (ADR-063)"):
23
+ * - `configurable: true | { min }` on an `every` or `cron` trigger lets an installation choose its own timing at
24
+ * `config.triggers.<name>` (`{ every }`, `{ cron, timezone }` or `{ enabled: false }`), never more often than `min`.
25
+ * A choice that breaks the floor, or a trigger that is not configurable, keeps the default (and is reported).
26
+ * - `cron` (five fields, with `timezone`) is a calendar trigger that may differ per installation. It runs off the
27
+ * shared tick: due when a firing falls in (previous tick, this tick]. `at` stays the ADR-055 calendar trigger,
28
+ * the same for every installation, with an exact schedule of its own.
29
+ */
30
+ const { parseEvery, validateRecurrence, minGapMinutes, lastFiringIn, isTimeZone } = require('../infrastructure/scheduler/recurrence');
31
+
32
+ const TRIGGER_RUN = 'FRIGG_TRIGGER_RUN';
33
+ /** A tick that ran out of time sends this to the integration's queue to carry on from its page cursor. */
34
+ const TRIGGER_TICK = 'FRIGG_TRIGGER_TICK';
35
+
36
+ const DEFAULT_TICK_MINUTES = 5;
37
+ const MAX_EVERY_MINUTES = 1440; // longer cadences use a calendar (`at` / `cron`)
38
+ const AT = /^cron\(\S+ \S+ \S+ \S+ \S+ \S+\)$/;
39
+ const OVERLAP = ['skip', 'allow'];
40
+
41
+ const configurableOf = (c) => (c ? (c === true ? {} : c) : false);
42
+ const kindOf = (t) => (t.at !== undefined ? 'at' : t.cron !== undefined ? 'cron' : 'every');
43
+ const recurrenceOf = (r) => (r?.every !== undefined ? { every: r.every } : r?.cron !== undefined ? { cron: r.cron, ...(r.timezone ? { timezone: r.timezone } : {}) } : null);
44
+
45
+ /** A minutes count as the shortest duration string: 90 -> '90m', 60 -> '1h', 1440 -> '1d'. */
46
+ function durationOf(minutes) {
47
+ if (minutes % 1440 === 0) return `${minutes / 1440}d`;
48
+ if (minutes % 60 === 0) return `${minutes / 60}h`;
49
+ return `${minutes}m`;
50
+ }
51
+
52
+ /**
53
+ * The tick a trigger runs on, in minutes. `tick` when set. Otherwise: for a constant `every`, that interval; for an
54
+ * `every` given as a function, 5 minutes; for a `cron`, 5 minutes, or less when the cron itself fires more often.
55
+ * A configurable trigger never ticks slower than its floor (or 5 minutes, without one), so the fastest choice it
56
+ * allows can run. `at` triggers have no tick (null): each has its own schedule.
57
+ */
58
+ function tickMinutesOf(trigger) {
59
+ if (kindOf(trigger) === 'at') return null;
60
+ if (trigger.tick !== undefined) return parseEvery(trigger.tick);
61
+ const configurable = configurableOf(trigger.configurable);
62
+ const floor = configurable ? (configurable.min !== undefined ? parseEvery(configurable.min) : DEFAULT_TICK_MINUTES) : Infinity;
63
+ let natural;
64
+ if (kindOf(trigger) === 'cron') {
65
+ const gap = minGapMinutes({ cron: trigger.cron });
66
+ natural = Math.max(1, Math.min(DEFAULT_TICK_MINUTES, gap ?? DEFAULT_TICK_MINUTES));
67
+ } else {
68
+ natural = typeof trigger.every === 'function' ? DEFAULT_TICK_MINUTES : parseEvery(trigger.every) ?? DEFAULT_TICK_MINUTES;
69
+ }
70
+ return Math.max(1, Math.min(natural, floor ?? DEFAULT_TICK_MINUTES, MAX_EVERY_MINUTES));
71
+ }
72
+
73
+ /**
74
+ * Problems with a trigger declaration, found without an installation (definition time and build time).
75
+ * @returns {string[]}
76
+ */
77
+ function validateTrigger(name, t) {
78
+ const at = (m) => `${name}: ${m}`;
79
+ if (!t || typeof t !== 'object') return [at('A trigger is an object')];
80
+ const problems = [];
81
+ const kinds = ['every', 'at', 'cron'].filter((k) => t[k] !== undefined);
82
+ if (kinds.length !== 1) return [at('Give exactly one of every, at or cron')];
83
+ if (typeof t.handler !== 'function' && typeof t.handler !== 'string') problems.push(at('handler must be a function or a method name'));
84
+ if (t.overlap !== undefined && !OVERLAP.includes(t.overlap)) problems.push(at(`overlap must be ${OVERLAP.join(' or ')}`));
85
+ if (t.when !== undefined && typeof t.when !== 'function') problems.push(at('when must be a function of the installation config'));
86
+ if (t.tick !== undefined) {
87
+ const tick = parseEvery(t.tick);
88
+ if (tick === null || tick > MAX_EVERY_MINUTES) problems.push(at(`tick "${t.tick}" is not a duration between 1m and 1d`));
89
+ }
90
+ const kind = kinds[0];
91
+ if (kind === 'at') {
92
+ if (typeof t.at !== 'string' || !AT.test(t.at)) problems.push(at(`at "${t.at}" must be an EventBridge cron(...) expression with six fields`));
93
+ if (t.timezone !== undefined && !isTimeZone(t.timezone)) problems.push(at(`Unknown time zone "${t.timezone}"`));
94
+ if (t.configurable) problems.push(at('An at trigger runs at one time for every installation; use cron to let installations choose'));
95
+ if (t.tick !== undefined) problems.push(at('An at trigger has its own schedule; tick does not apply'));
96
+ return problems;
97
+ }
98
+ const configurable = configurableOf(t.configurable);
99
+ if (configurable && configurable.min !== undefined && parseEvery(configurable.min) === null) problems.push(at(`configurable.min "${configurable.min}" is not a duration (15m, 2h, 1d)`));
100
+ if (kind === 'every' && typeof t.every === 'function') return problems;
101
+ const recurrence = recurrenceOf(t);
102
+ for (const p of validateRecurrence(recurrence)) problems.push(at(p));
103
+ if (kind === 'every' && parseEvery(t.every) > MAX_EVERY_MINUTES) problems.push(at(`every "${t.every}" is longer than 1d; use at or cron`));
104
+ if (!problems.length && configurable && configurable.min !== undefined) {
105
+ for (const p of validateRecurrence(recurrence, { min: configurable.min })) problems.push(at(`The default breaks its own minimum: ${p}`));
106
+ }
107
+ return problems;
108
+ }
109
+
110
+ /** Problems with every trigger a Definition declares. */
111
+ function validateTriggers(triggers = {}) {
112
+ return Object.entries(triggers || {}).flatMap(([name, t]) => validateTrigger(name, t));
113
+ }
114
+
115
+ /**
116
+ * What devtools provisions for an integration class: the distinct ticks (minutes) of its `every`/`cron` triggers,
117
+ * including the tick of the triggers it resolves per installation (`Definition.instanceTriggers`), and one entry per
118
+ * `at` trigger. Throws on an invalid declaration, so a bad trigger fails the build rather than the first tick.
119
+ *
120
+ * @returns {{ ticks: number[], at: Array<{ name, at, timezone? }> }}
121
+ */
122
+ function provisioningOf(IntegrationClass) {
123
+ const Definition = IntegrationClass?.Definition || {};
124
+ const triggers = Definition.triggers || {};
125
+ const problems = validateTriggers(triggers);
126
+ const instance = Definition.instanceTriggers;
127
+ if (instance && parseEvery(instance.tick) === null) problems.push(`instanceTriggers.tick "${instance.tick}" is not a duration`);
128
+ if (problems.length) throw new Error(`Integration "${Definition.name}" declares invalid triggers: ${problems.join('; ')}`);
129
+ const ticks = new Set();
130
+ const at = [];
131
+ for (const [name, t] of Object.entries(triggers)) {
132
+ if (kindOf(t) === 'at') at.push({ name, at: t.at, ...(t.timezone ? { timezone: t.timezone } : {}) });
133
+ else ticks.add(tickMinutesOf(t));
134
+ }
135
+ if (instance) ticks.add(parseEvery(instance.tick));
136
+ return { ticks: [...ticks].sort((a, b) => a - b), at };
137
+ }
138
+
139
+ /** Does this class declare any triggers (fixed or per installation)? */
140
+ function hasTriggers(IntegrationClass) {
141
+ const Definition = IntegrationClass?.Definition || {};
142
+ return Object.keys(Definition.triggers || {}).length > 0 || Boolean(Definition.instanceTriggers);
143
+ }
144
+
145
+ /**
146
+ * One trigger, resolved for one installation.
147
+ *
148
+ * @param {string} name
149
+ * @param {Object} t - the declaration (plus `choice` when the caller found the installation's choice elsewhere)
150
+ * @param {Object} config - the installation's config
151
+ * @returns {{ name, kind, recurrence?, at?, tickMinutes, cadence?, overlap, active: boolean, inactive?: string,
152
+ * source: 'definition'|'installation', problems: string[] }}
153
+ * `active: false` when `when` excludes the installation or it paused a configurable trigger.
154
+ * An invalid choice keeps the default and is listed in `problems`, so a bad edit never stops a trigger.
155
+ */
156
+ function resolveTrigger(name, t, config = {}) {
157
+ const kind = kindOf(t);
158
+ const out = { name, kind, tickMinutes: tickMinutesOf(t), overlap: t.overlap || 'skip', active: true, source: 'definition', problems: [] };
159
+ if (typeof t.when === 'function' && !t.when(config)) return { ...out, active: false, inactive: 'when' };
160
+ if (kind === 'at') return { ...out, at: t.at, timezone: t.timezone };
161
+
162
+ let recurrence = kind === 'every' ? { every: typeof t.every === 'function' ? t.every(config) : t.every } : recurrenceOf(t);
163
+ const configurable = configurableOf(t.configurable);
164
+ const choice = t.choice !== undefined ? t.choice : config?.triggers?.[name];
165
+ if (choice && typeof choice === 'object' && (recurrenceOf(choice) || choice.enabled !== undefined)) {
166
+ if (!configurable) out.problems.push('This trigger is not configurable');
167
+ else {
168
+ const picked = recurrenceOf(choice);
169
+ const issues = picked ? validateRecurrence(picked, { min: configurable.min }) : [];
170
+ if (picked?.every !== undefined && !issues.length && parseEvery(picked.every) > MAX_EVERY_MINUTES) issues.push(`every "${picked.every}" is longer than 1d`);
171
+ if (issues.length) out.problems.push(...issues);
172
+ else {
173
+ if (picked) { recurrence = picked; out.source = 'installation'; }
174
+ if (choice.enabled === false) return { ...out, recurrence, active: false, inactive: 'paused' };
175
+ }
176
+ }
177
+ }
178
+ const own = validateRecurrence(recurrence);
179
+ if (recurrence.every !== undefined && !own.length && parseEvery(recurrence.every) > MAX_EVERY_MINUTES) own.push(`every "${recurrence.every}" is longer than 1d`);
180
+ if (own.length) return { ...out, recurrence, active: false, inactive: 'invalid', problems: [...out.problems, ...own] };
181
+ out.recurrence = recurrence;
182
+ // An interval runs every n ticks (rounded up to a whole number of ticks).
183
+ if (recurrence.every !== undefined) out.cadence = Math.max(1, Math.ceil(parseEvery(recurrence.every) / out.tickMinutes));
184
+ return out;
185
+ }
186
+
187
+ /** A stable, evenly spread number for an installation id (FNV-1a, 32 bits). */
188
+ function hashId(value) {
189
+ let h = 0x811c9dc5;
190
+ for (const ch of String(value)) {
191
+ h ^= ch.codePointAt(0);
192
+ h = Math.imul(h, 0x01000193) >>> 0;
193
+ }
194
+ return h;
195
+ }
196
+
197
+ /** The start of the tick slot an instant falls in: tick runs are keyed by slot, so a late or repeated tick agrees. */
198
+ function slotStart(timeMs, tickMinutes) {
199
+ const size = tickMinutes * 60000;
200
+ return Math.floor(timeMs / size) * size;
201
+ }
202
+
203
+ /**
204
+ * Is a resolved trigger due for this installation at this tick slot?
205
+ *
206
+ * - every: due when `slot mod n == hash(integrationId) mod n` (n = cadence in ticks), so it is stateless and spreads
207
+ * installations evenly across the cadence. scheduledFor is the slot start.
208
+ * - cron: due when it fired in (slot start - tick, slot start]. scheduledFor is that firing, so two ticks in one slot
209
+ * (a redelivery, a retried continuation) name the same run.
210
+ * - at: due whenever its own schedule invoked the tick; scheduledFor is that time, to the minute.
211
+ *
212
+ * @param {Object} resolved - from resolveTrigger
213
+ * @param {{ integrationId, slotMs: number, firing?: (recurrence, from, to) => number|null }} at
214
+ * @returns {{ scheduledFor: string } | null}
215
+ */
216
+ function dueRun(resolved, { integrationId, slotMs, firing = lastFiringIn }) {
217
+ if (!resolved?.active) return null;
218
+ if (resolved.kind === 'at') return { scheduledFor: new Date(Math.floor(slotMs / 60000) * 60000).toISOString() };
219
+ if (resolved.recurrence.every !== undefined) {
220
+ const n = resolved.cadence;
221
+ const slot = Math.floor(slotMs / (resolved.tickMinutes * 60000));
222
+ return slot % n === hashId(integrationId) % n ? { scheduledFor: new Date(slotMs).toISOString() } : null;
223
+ }
224
+ const fired = firing(resolved.recurrence, slotMs - resolved.tickMinutes * 60000, slotMs);
225
+ return fired === null ? null : { scheduledFor: new Date(fired).toISOString() };
226
+ }
227
+
228
+ module.exports = {
229
+ TRIGGER_RUN,
230
+ TRIGGER_TICK,
231
+ DEFAULT_TICK_MINUTES,
232
+ configurableOf,
233
+ recurrenceOf,
234
+ durationOf,
235
+ tickMinutesOf,
236
+ validateTrigger,
237
+ validateTriggers,
238
+ provisioningOf,
239
+ hasTriggers,
240
+ resolveTrigger,
241
+ hashId,
242
+ slotStart,
243
+ dueRun,
244
+ };
@@ -27,19 +27,13 @@ class CreateIntegration {
27
27
  * and entity set, instead of creating a duplicate.
28
28
  * @async
29
29
  * @param {string[]} entities - Array of entity IDs to associate with the integration.
30
- * @param {string} userId - ID of the OWNER of the integration (ADR-056 §3):
31
- * the person or the organization's user, as ResolveIntegrationOwnership decided.
30
+ * @param {string} userId - ID of the user creating the integration.
32
31
  * @param {Object} config - Configuration object for the integration.
33
32
  * @param {string} config.type - Type of integration to create.
34
- * @param {{organizationId?: (string|null), adoptUnscoped?: boolean}} [tenancy]
35
- * the organization it is created in (§3.6). A duplicate is only reused in
36
- * the same organization, so one person's integration in organization A is
37
- * not handed back as the one they asked for in B. `adoptUnscoped` also
38
- * accepts a duplicate written before scoping (no organization).
39
33
  * @returns {Promise<Object>} The created or reused integration DTO.
40
34
  * @throws {Error} When integration class is not found for the specified type.
41
35
  */
42
- async execute(entities, userId, config, tenancy = {}) {
36
+ async execute(entities, userId, config) {
43
37
  console.log(
44
38
  `[Frigg] Creating ${config?.type} integration for user ${userId} with entities [${entities}]`
45
39
  );
@@ -47,8 +41,7 @@ class CreateIntegration {
47
41
  const existing = await this._findDuplicate(
48
42
  userId,
49
43
  config?.type,
50
- entities,
51
- tenancy
44
+ entities
52
45
  );
53
46
  if (existing) {
54
47
  console.log(
@@ -57,18 +50,11 @@ class CreateIntegration {
57
50
  return this._reuseExisting(existing, config);
58
51
  }
59
52
 
60
- // Everything that can be refused is refused before a row is written: an
61
- // unknown type, or an entity the owner cannot load, would otherwise
62
- // leave an IN_CREATION integration behind that nothing moves on.
63
- const integrationClass = this._requireClass(config?.type);
64
- const modules = await this._loadModules(entities, userId);
65
-
66
53
  const integrationRecord =
67
54
  await this.integrationRepository.createIntegration(
68
55
  entities,
69
56
  userId,
70
- config,
71
- { organizationId: tenancy.organizationId ?? null }
57
+ config
72
58
  );
73
59
  console.log(
74
60
  `[Frigg] Created integration ${integrationRecord.id} for user ${userId}`
@@ -79,8 +65,7 @@ class CreateIntegration {
79
65
  const survivor = await this._findDuplicate(
80
66
  userId,
81
67
  config?.type,
82
- entities,
83
- tenancy
68
+ entities
84
69
  );
85
70
  if (survivor && String(survivor.id) !== String(integrationRecord.id)) {
86
71
  console.log(
@@ -93,8 +78,7 @@ class CreateIntegration {
93
78
  }
94
79
 
95
80
  const integrationInstance = await this._buildInstance(
96
- integrationRecord,
97
- { integrationClass, modules }
81
+ integrationRecord
98
82
  );
99
83
  console.log(
100
84
  `[Frigg] Sending ON_CREATE for integration ${integrationRecord.id}`
@@ -151,7 +135,7 @@ class CreateIntegration {
151
135
  return mapIntegrationClassToIntegrationDTO(integrationInstance);
152
136
  }
153
137
 
154
- async _findDuplicate(userId, type, entityIds, tenancy = {}) {
138
+ async _findDuplicate(userId, type, entityIds) {
155
139
  const candidates =
156
140
  await this.integrationRepository.findIntegrationsByUserId(userId);
157
141
  const target = [...(entityIds ?? [])].map(String).sort();
@@ -166,7 +150,6 @@ class CreateIntegration {
166
150
  // matching how teardown failures are already handled here.
167
151
  if (integration.status === 'IN_DELETION') return false;
168
152
  if (integration.config?.type !== type) return false;
169
- if (!this._sameOrganization(integration, tenancy)) return false;
170
153
  const current = [...(integration.entitiesIds ?? [])]
171
154
  .map(String)
172
155
  .sort();
@@ -179,15 +162,6 @@ class CreateIntegration {
179
162
  return matches.length ? this._pickOldest(matches) : null;
180
163
  }
181
164
 
182
- /** Same organization, or a row from before scoping that belongs here. */
183
- _sameOrganization(integration, tenancy) {
184
- const wanted = tenancy.organizationId ?? null;
185
- const has = integration.organizationId ?? null;
186
- if (wanted === null && has === null) return true;
187
- if (wanted !== null && has !== null) return String(wanted) === String(has);
188
- return has === null && tenancy.adoptUnscoped === true;
189
- }
190
-
191
165
  _pickOldest(records) {
192
166
  return [...records].sort((a, b) => {
193
167
  const diff =
@@ -199,36 +173,27 @@ class CreateIntegration {
199
173
  })[0];
200
174
  }
201
175
 
202
- _requireClass(type) {
176
+ async _buildInstance(integrationRecord) {
203
177
  const integrationClass = this.integrationClasses.find(
204
- (integrationClass) => integrationClass.Definition.name === type
178
+ (integrationClass) =>
179
+ integrationClass.Definition.name ===
180
+ integrationRecord.config.type
205
181
  );
182
+
206
183
  if (!integrationClass) {
207
- throw new Error(`No integration class found for type: ${type}`);
184
+ throw new Error(
185
+ `No integration class found for type: ${integrationRecord.config.type}`
186
+ );
208
187
  }
209
- return integrationClass;
210
- }
211
188
 
212
- async _loadModules(entityIds, userId) {
213
189
  const modules = [];
214
- for (const entityId of entityIds) {
215
- modules.push(
216
- await this.moduleFactory.getModuleInstance(entityId, userId)
190
+ for (const entityId of integrationRecord.entitiesIds) {
191
+ const moduleInstance = await this.moduleFactory.getModuleInstance(
192
+ entityId,
193
+ integrationRecord.userId
217
194
  );
195
+ modules.push(moduleInstance);
218
196
  }
219
- return modules;
220
- }
221
-
222
- async _buildInstance(integrationRecord, preloaded) {
223
- const integrationClass =
224
- preloaded?.integrationClass ??
225
- this._requireClass(integrationRecord.config.type);
226
- const modules =
227
- preloaded?.modules ??
228
- (await this._loadModules(
229
- integrationRecord.entitiesIds,
230
- integrationRecord.userId
231
- ));
232
197
 
233
198
  const integrationInstance = new integrationClass({
234
199
  id: integrationRecord.id,
@@ -2,17 +2,9 @@
2
2
  const {
3
3
  mapIntegrationClassToIntegrationDTO,
4
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');
11
5
 
12
6
  /**
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}).
7
+ * Use case for retrieving all integrations for a specific user.
16
8
  * @class GetIntegrationsForUser
17
9
  */
18
10
  class GetIntegrationsForUser {
@@ -23,16 +15,13 @@ class GetIntegrationsForUser {
23
15
  * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
24
16
  * @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
25
17
  * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
26
- * @param {Object} [params.scopePolicy] - from createScopePolicy
27
18
  */
28
19
  constructor({
29
20
  integrationRepository,
30
21
  integrationClasses,
31
22
  moduleFactory,
32
23
  moduleRepository,
33
- scopePolicy,
34
24
  }) {
35
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
36
25
  /**
37
26
  * @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
38
27
  */
@@ -43,86 +32,57 @@ class GetIntegrationsForUser {
43
32
  }
44
33
 
45
34
  /**
35
+ * Executes the retrieval of all integrations for a user.
46
36
  * @async
47
- * @param {Object} user - authenticated User
48
- * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
49
- * @returns {Promise<Object[]>} Array of integration DTOs.
37
+ * @param {string} userId - ID of the user whose integrations to retrieve.
38
+ * @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
50
39
  */
51
- async execute(user, filters = {}) {
52
- const ctx = this.scopePolicy.accessContext(user);
53
- const records = await this.integrationRepository.findIntegrationsByOwnership(
54
- visibilityClauses(ctx, 'integration')
55
- );
40
+ async execute(userId) {
41
+ const integrationRecords =
42
+ await this.integrationRepository.findIntegrationsByUserId(userId);
56
43
 
57
44
  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
- }
67
45
 
68
- /** Hydrates one stored integration record into its DTO. */
69
- async toDto(integrationRecord) {
70
- const entities = await this.moduleRepository.findEntitiesByIds(
71
- integrationRecord.entitiesIds
72
- );
73
-
74
- const integrationClass = this.integrationClasses.find(
75
- (integrationClass) =>
76
- integrationClass.Definition.name ===
77
- integrationRecord.config.type
78
- );
46
+ for (const integrationRecord of integrationRecords) {
47
+ const entities = await this.moduleRepository.findEntitiesByIds(
48
+ integrationRecord.entitiesIds
49
+ );
79
50
 
80
- const modules = [];
81
- for (const entity of entities) {
82
- const moduleInstance = await this.moduleFactory.getModuleInstance(
83
- entity.id,
84
- integrationRecord.userId
51
+ const integrationClass = this.integrationClasses.find(
52
+ (integrationClass) =>
53
+ integrationClass.Definition.name ===
54
+ integrationRecord.config.type
85
55
  );
86
- modules.push(moduleInstance);
87
- }
88
56
 
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
- }
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
+ }
102
66
 
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
- }
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
+ };
114
78
 
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));
79
+ integrations.push(
80
+ mapIntegrationClassToIntegrationDTO(integrationData)
81
+ );
123
82
  }
83
+
124
84
  return integrations;
125
85
  }
126
86
  }
127
87
 
128
- module.exports = { GetIntegrationsForUser, GetIntegrationsByUserId };
88
+ module.exports = { GetIntegrationsForUser };
@@ -1,7 +1,4 @@
1
- const {
2
- GetIntegrationsForUser,
3
- GetIntegrationsByUserId,
4
- } = require('./get-integrations-for-user');
1
+ const { GetIntegrationsForUser } = require('./get-integrations-for-user');
5
2
  const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
6
3
  const { CreateIntegration } = require('./create-integration');
7
4
  const { GetIntegration } = require('./get-integration');
@@ -9,11 +6,9 @@ const { CreateProcess } = require('./create-process');
9
6
  const { UpdateProcessState } = require('./update-process-state');
10
7
  const { UpdateProcessMetrics } = require('./update-process-metrics');
11
8
  const { GetProcess } = require('./get-process');
12
- const { DispatchOngoingSyncs } = require('./dispatch-ongoing-syncs');
13
9
 
14
10
  module.exports = {
15
11
  GetIntegrationsForUser,
16
- GetIntegrationsByUserId,
17
12
  DeleteIntegrationForUser,
18
13
  CreateIntegration,
19
14
  GetIntegration,
@@ -21,5 +16,4 @@ module.exports = {
21
16
  UpdateProcessState,
22
17
  UpdateProcessMetrics,
23
18
  GetProcess,
24
- DispatchOngoingSyncs,
25
19
  };