@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,5 +1,4 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { MappingAlreadyExistsError } = require('../../errors');
3
2
  const {
4
3
  IntegrationMappingRepositoryInterface,
5
4
  } = require('./integration-mapping-repository-interface');
@@ -70,34 +69,6 @@ class IntegrationMappingRepository extends IntegrationMappingRepositoryInterface
70
69
  });
71
70
  }
72
71
 
73
- /**
74
- * Insert a mapping; fails if (integrationId, sourceId) already exists
75
- *
76
- * @param {string} integrationId - The integration ID
77
- * @param {string} sourceId - The source ID
78
- * @param {Object} mapping - The mapping data
79
- * @returns {Promise<Object>} The created mapping document
80
- * @throws {MappingAlreadyExistsError} If the pair already exists
81
- */
82
- async createMapping(integrationId, sourceId, mapping) {
83
- try {
84
- return await this.prisma.integrationMapping.create({
85
- data: {
86
- integrationId,
87
- sourceId,
88
- mapping,
89
- },
90
- });
91
- } catch (error) {
92
- if (error.code === 'P2002') {
93
- throw new MappingAlreadyExistsError(integrationId, sourceId, {
94
- cause: error,
95
- });
96
- }
97
- throw error;
98
- }
99
- }
100
-
101
72
  /**
102
73
  * Find all mappings for an integration
103
74
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The error `findIntegrationById` throws for an id with no row, and the one
3
+ * predicate that recognises it. The adapters build the error here and the
4
+ * use cases match it here, so the message is written once and a missing
5
+ * integration can be told from any other failure without widening a regex.
6
+ */
7
+ const MESSAGE = /^Integration with id .+ not found$/;
8
+
9
+ const integrationNotFoundError = (id) =>
10
+ new Error(`Integration with id ${id} not found`);
11
+
12
+ const isIntegrationNotFound = (error) => MESSAGE.test(error?.message || '');
13
+
14
+ module.exports = { integrationNotFoundError, isIntegrationNotFound };
@@ -14,6 +14,7 @@ const {
14
14
  IntegrationRepositoryInterface,
15
15
  } = require('./integration-repository-interface');
16
16
  const { validateConfigPatch } = require('./config-patch-shared');
17
+ const { integrationNotFoundError } = require('./integration-not-found');
17
18
 
18
19
  class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
19
20
  constructor() {
@@ -28,7 +29,29 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
28
29
  return records.map((doc) => this._mapIntegration(doc));
29
30
  }
30
31
 
31
- async findIntegrations({ type, status, limit, after } = {}) {
32
+ /**
33
+ * Find integrations matching any of the owner/organization pairs, in one
34
+ * query. `organizationId: null` matches rows with no organization (a raw
35
+ * MongoDB `null` also matches a missing field). The caller (a use case)
36
+ * decides which pairs; this only runs them.
37
+ *
38
+ * @param {Array<{userId: string, organizationId: (string|null)}>} clauses
39
+ * @returns {Promise<Array>}
40
+ */
41
+ async findIntegrationsByOwnership(clauses) {
42
+ if (!Array.isArray(clauses) || clauses.length === 0) return [];
43
+ // Drained: a list can hold more than the first cursor batch (~101).
44
+ const records = await findManyDrained(this.prisma, 'Integration', {
45
+ $or: clauses.map(({ userId, organizationId }) => ({
46
+ userId: toObjectId(userId),
47
+ organizationId:
48
+ organizationId === null ? null : toObjectId(organizationId),
49
+ })),
50
+ });
51
+ return records.map((doc) => this._mapIntegration(doc));
52
+ }
53
+
54
+ async findIntegrations({ type, status } = {}) {
32
55
  const filter = {};
33
56
  if (type) {
34
57
  filter['config.type'] = type;
@@ -36,11 +59,7 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
36
59
  if (status) {
37
60
  filter.status = status;
38
61
  }
39
- if (limit && after !== undefined && after !== null) {
40
- // Extended JSON: $runCommandRaw does not match a BSON ObjectId instance inside an operator.
41
- filter._id = { $gt: { $oid: fromObjectId(toObjectId(after)) } };
42
- }
43
- const records = await findMany(this.prisma, 'Integration', filter, limit ? { sort: { _id: 1 }, limit, batchSize: limit } : {});
62
+ const records = await findMany(this.prisma, 'Integration', filter);
44
63
  return records.map((doc) => this._mapIntegration(doc));
45
64
  }
46
65
 
@@ -72,11 +91,11 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
72
91
  async findIntegrationById(id) {
73
92
  const objectId = toObjectId(id);
74
93
  if (!objectId) {
75
- throw new Error(`Integration with id ${id} not found`);
94
+ throw integrationNotFoundError(id);
76
95
  }
77
96
  const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
78
97
  if (!doc) {
79
- throw new Error(`Integration with id ${id} not found`);
98
+ throw integrationNotFoundError(id);
80
99
  }
81
100
  return this._mapIntegration(doc);
82
101
  }
@@ -136,10 +155,18 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
136
155
  return true;
137
156
  }
138
157
 
139
- async createIntegration(entities, userId, config) {
158
+ /**
159
+ * @param {Array<string>} entities - entity ids
160
+ * @param {string} userId - owner (the person, or the organization's user)
161
+ * @param {Object} config - Integration configuration
162
+ * @param {{organizationId?: (string|null)}} [tenancy] - the organization the
163
+ * integration is created in (ADR-056 §3.6)
164
+ */
165
+ async createIntegration(entities, userId, config, tenancy = {}) {
140
166
  const now = new Date();
141
167
  const document = {
142
168
  userId: toObjectId(userId) || null,
169
+ organizationId: toObjectId(tenancy.organizationId) || null,
143
170
  config,
144
171
  version: '0.0.0',
145
172
  status: 'IN_CREATION',
@@ -302,6 +329,7 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
302
329
  id: fromObjectId(doc?._id),
303
330
  entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
304
331
  userId: fromObjectId(doc?.userId),
332
+ organizationId: fromObjectId(doc?.organizationId) ?? null,
305
333
  config: doc?.config ?? null,
306
334
  version: doc?.version ?? null,
307
335
  status: doc?.status ?? null,
@@ -10,6 +10,20 @@
10
10
  * @abstract
11
11
  */
12
12
  class IntegrationRepositoryInterface {
13
+ /**
14
+ * Find integrations matching any of the owner/organization pairs, in one
15
+ * query (ADR-056 §3.6). `organizationId: null` matches rows with no
16
+ * organization. Which pairs is decided by the caller
17
+ * (modules/entity-scope.js), not here.
18
+ *
19
+ * @param {Array<{userId: string|number, organizationId: (string|number|null)}>} clauses
20
+ * @returns {Promise<Array>} Array of integration objects
21
+ * @abstract
22
+ */
23
+ async findIntegrationsByOwnership(clauses) {
24
+ throw new Error('Method findIntegrationsByOwnership must be implemented by subclass');
25
+ }
26
+
13
27
  /**
14
28
  * Find all integrations for a user
15
29
  *
@@ -28,9 +42,6 @@ class IntegrationRepositoryInterface {
28
42
  * @param {Object} [filter={}]
29
43
  * @param {string} [filter.type] - Integration type (config.type)
30
44
  * @param {string} [filter.status] - Integration status
31
- * @param {number} [filter.limit] - Page size: at most this many rows, in id order
32
- * @param {string|number} [filter.after] - With limit: only rows whose id sorts after this one (the last id of the
33
- * previous page), so a caller can page through every row (the trigger tick does, ADR-055)
34
45
  * @returns {Promise<Array>} Array of integration objects (possibly empty)
35
46
  * @abstract
36
47
  */
@@ -126,10 +137,12 @@ class IntegrationRepositoryInterface {
126
137
  * @param {Array<string|number>} entities - Array of entity IDs
127
138
  * @param {string|number} userId - User ID
128
139
  * @param {Object} config - Integration configuration
140
+ * @param {{organizationId?: (string|number|null)}} [tenancy] - the organization the
141
+ * integration is created in (ADR-056 §3.6)
129
142
  * @returns {Promise<Object>} Created integration object
130
143
  * @abstract
131
144
  */
132
- async createIntegration(entities, userId, config) {
145
+ async createIntegration(entities, userId, config, tenancy) {
133
146
  throw new Error('Method createIntegration must be implemented by subclass');
134
147
  }
135
148
 
@@ -1,8 +1,10 @@
1
1
  const { prisma } = require('../../database/prisma');
2
+ const { organizationWhere } = require('../../database/organization-where');
2
3
  const {
3
4
  IntegrationRepositoryInterface,
4
5
  } = require('./integration-repository-interface');
5
6
  const { validateConfigPatch } = require('./config-patch-shared');
7
+ const { integrationNotFoundError } = require('./integration-not-found');
6
8
 
7
9
  /**
8
10
  * MongoDB Integration Repository Adapter
@@ -25,6 +27,25 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
25
27
  this.prisma = prisma;
26
28
  }
27
29
 
30
+ /**
31
+ * The one place a integration integration row becomes the record the
32
+ * application sees (ids of its entities, its organization).
33
+ * @private
34
+ */
35
+ _mapIntegration(integration) {
36
+ return {
37
+ id: integration.id,
38
+ entitiesIds: integration.entities.map((e) => e.id),
39
+ userId: integration.userId,
40
+ organizationId: integration.organizationId ?? null,
41
+ config: integration.config,
42
+ version: integration.version,
43
+ status: integration.status,
44
+ messages: integration.messages,
45
+ createdAt: integration.createdAt,
46
+ };
47
+ }
48
+
28
49
  /**
29
50
  * Find all integrations for a user
30
51
  * Replaces: IntegrationModel.find({ user: userId }).populate('entities')
@@ -41,16 +62,32 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
41
62
  });
42
63
 
43
64
  // Map to domain objects (maintains same API)
44
- return integrations.map((integration) => ({
45
- id: integration.id,
46
- entitiesIds: integration.entities.map((e) => e.id),
47
- userId: integration.userId,
48
- config: integration.config,
49
- version: integration.version,
50
- status: integration.status,
51
- messages: integration.messages,
52
- createdAt: integration.createdAt,
53
- }));
65
+ return integrations.map((integration) => (this._mapIntegration(integration)));
66
+ }
67
+
68
+ /**
69
+ * Find integrations matching any of the owner/organization pairs, in one
70
+ * query. `organizationId: null` matches rows with no organization. The
71
+ * caller (a use case) decides which pairs; this only runs them.
72
+ *
73
+ * @param {Array<{userId: string, organizationId: (string|null)}>} clauses
74
+ * @returns {Promise<Array>} Array of integration objects
75
+ */
76
+ async findIntegrationsByOwnership(clauses) {
77
+ if (!Array.isArray(clauses) || clauses.length === 0) return [];
78
+ const integrations = await this.prisma.integration.findMany({
79
+ where: {
80
+ OR: clauses.map(({ userId, organizationId }) => ({
81
+ userId,
82
+ ...organizationWhere(organizationId),
83
+ })),
84
+ },
85
+ include: {
86
+ entities: true,
87
+ },
88
+ });
89
+
90
+ return integrations.map((integration) => (this._mapIntegration(integration)));
54
91
  }
55
92
 
56
93
  /**
@@ -61,47 +98,23 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
61
98
  * @param {string} [filter.status] - Integration status
62
99
  * @returns {Promise<Array>} Array of integration objects (possibly empty)
63
100
  */
64
- async findIntegrations({ type, status, limit, after } = {}) {
101
+ async findIntegrations({ type, status } = {}) {
65
102
  const where = {};
103
+ if (type) {
104
+ where.config = { path: ['type'], equals: type };
105
+ }
66
106
  if (status) {
67
107
  where.status = status;
68
108
  }
69
- if (limit && after !== undefined && after !== null) {
70
- where.id = { gt: after };
71
- }
72
- if (type) {
73
- // Prisma's MongoDB connector has no JSON path filter (`config: { path }` is refused), so the ids of the
74
- // matching rows come from a raw find on config.type, then the rows load as usual.
75
- const filter = { 'config.type': type };
76
- if (status) filter.status = status;
77
- if (where.id) filter._id = { $gt: { $oid: String(after) } };
78
- const found = await this.prisma.integration.findRaw({
79
- filter,
80
- options: { projection: { _id: 1 }, sort: { _id: 1 }, ...(limit ? { limit } : {}) },
81
- });
82
- const ids = found.map((doc) => doc._id?.$oid ?? String(doc._id));
83
- if (!ids.length) return [];
84
- where.id = { in: ids };
85
- }
86
109
 
87
110
  const integrations = await this.prisma.integration.findMany({
88
111
  where,
89
112
  include: {
90
113
  entities: true,
91
114
  },
92
- ...(limit ? { orderBy: { id: 'asc' }, take: limit } : {}),
93
115
  });
94
116
 
95
- return integrations.map((integration) => ({
96
- id: integration.id,
97
- entitiesIds: integration.entities.map((e) => e.id),
98
- userId: integration.userId,
99
- config: integration.config,
100
- version: integration.version,
101
- status: integration.status,
102
- messages: integration.messages,
103
- createdAt: integration.createdAt,
104
- }));
117
+ return integrations.map((integration) => (this._mapIntegration(integration)));
105
118
  }
106
119
 
107
120
  /**
@@ -180,15 +193,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
180
193
  throw new Error(`Integration with name ${name} not found`);
181
194
  }
182
195
 
183
- return {
184
- id: integration.id,
185
- entitiesIds: integration.entities.map((e) => e.id),
186
- userId: integration.userId,
187
- config: integration.config,
188
- version: integration.version,
189
- status: integration.status,
190
- messages: integration.messages,
191
- };
196
+ return this._mapIntegration(integration);
192
197
  }
193
198
 
194
199
  /**
@@ -207,18 +212,10 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
207
212
  });
208
213
 
209
214
  if (!integration) {
210
- throw new Error(`Integration with id ${id} not found`);
215
+ throw integrationNotFoundError(id);
211
216
  }
212
217
 
213
- return {
214
- id: integration.id,
215
- entitiesIds: integration.entities.map((e) => e.id),
216
- userId: integration.userId,
217
- config: integration.config,
218
- version: integration.version,
219
- status: integration.status,
220
- messages: integration.messages,
221
- };
218
+ return this._mapIntegration(integration);
222
219
  }
223
220
 
224
221
  /**
@@ -305,15 +302,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
305
302
  },
306
303
  });
307
304
 
308
- return integrations.map((integration) => ({
309
- id: integration.id,
310
- entitiesIds: integration.entities.map((e) => e.id),
311
- userId: integration.userId,
312
- config: integration.config,
313
- version: integration.version,
314
- status: integration.status,
315
- messages: integration.messages,
316
- }));
305
+ return integrations.map((integration) => (this._mapIntegration(integration)));
317
306
  }
318
307
 
319
308
  /**
@@ -325,13 +314,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
325
314
  * @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
326
315
  * @param {string} userId - User ID (MongoDB ObjectId)
327
316
  * @param {Object} config - Integration configuration
317
+ * @param {{organizationId?: (string|null)}} [tenancy] - the organization the
318
+ * integration is created in (ADR-056 §3.6)
328
319
  * @returns {Promise<Object>} Created integration object
329
320
  */
330
- async createIntegration(entities, userId, config) {
321
+ async createIntegration(entities, userId, config, tenancy = {}) {
331
322
  const data = {
332
323
  config,
333
324
  version: '0.0.0',
334
325
  userId: userId,
326
+ organizationId: tenancy.organizationId ?? null,
335
327
  entityIds: entities,
336
328
  };
337
329
 
@@ -342,15 +334,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
342
334
  },
343
335
  });
344
336
 
345
- return {
346
- id: integration.id,
347
- entitiesIds: integration.entities.map((e) => e.id),
348
- userId: integration.userId,
349
- config: integration.config,
350
- version: integration.version,
351
- status: integration.status,
352
- messages: integration.messages,
353
- };
337
+ return this._mapIntegration(integration);
354
338
  }
355
339
 
356
340
  /**
@@ -372,15 +356,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
372
356
  return null;
373
357
  }
374
358
 
375
- return {
376
- id: integration.id,
377
- entitiesIds: integration.entities.map((e) => e.id),
378
- userId: integration.userId,
379
- config: integration.config,
380
- version: integration.version,
381
- status: integration.status,
382
- messages: integration.messages,
383
- };
359
+ return this._mapIntegration(integration);
384
360
  }
385
361
 
386
362
  /**
@@ -404,15 +380,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
404
380
  },
405
381
  });
406
382
 
407
- return {
408
- id: integration.id,
409
- entitiesIds: integration.entities.map((e) => e.id),
410
- userId: integration.userId,
411
- config: integration.config,
412
- version: integration.version,
413
- status: integration.status,
414
- messages: integration.messages,
415
- };
383
+ return this._mapIntegration(integration);
416
384
  }
417
385
 
418
386
  /**
@@ -434,7 +402,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
434
402
  for (const [key, value] of Object.entries(patch)) {
435
403
  $set[`config.${key}`] = value;
436
404
  }
437
- $set.updatedAt = { $date: new Date().toISOString() };
405
+ $set.updatedAt = new Date();
438
406
 
439
407
  const result = await this.prisma.$runCommandRaw({
440
408
  findAndModify: 'Integration',