@friggframework/core 2.0.0--canary.690.08cd07e.0 → 2.0.0--canary.693.d8859c2.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 (261) 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 +0 -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/index.js +3 -65
  44. package/infrastructure/scheduler/index.js +6 -19
  45. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  46. package/integrations/data-defined/README.md +169 -0
  47. package/integrations/data-defined/canonical.js +11 -0
  48. package/integrations/data-defined/capabilities/acmeerp.capabilities.json +608 -0
  49. package/integrations/data-defined/capabilities/asana.capabilities.json +265 -0
  50. package/integrations/data-defined/capabilities/calendly.capabilities.json +385 -0
  51. package/integrations/data-defined/capabilities/docusign.capabilities.json +314 -0
  52. package/integrations/data-defined/capabilities/fathom.capabilities.json +228 -0
  53. package/integrations/data-defined/capabilities/github.capabilities.json +563 -0
  54. package/integrations/data-defined/capabilities/hubspot.capabilities.json +715 -0
  55. package/integrations/data-defined/capabilities/jira.capabilities.json +376 -0
  56. package/integrations/data-defined/capabilities/klaviyo.capabilities.json +475 -0
  57. package/integrations/data-defined/capabilities/linear.capabilities.json +324 -0
  58. package/integrations/data-defined/capabilities/netsuite.capabilities.json +491 -0
  59. package/integrations/data-defined/capabilities/notion.capabilities.json +208 -0
  60. package/integrations/data-defined/capabilities/phone.capabilities.json +470 -0
  61. package/integrations/data-defined/capabilities/pipedrive.capabilities.json +388 -0
  62. package/integrations/data-defined/capabilities/quickbooks.capabilities.json +615 -0
  63. package/integrations/data-defined/capabilities/salesforce.capabilities.json +507 -0
  64. package/integrations/data-defined/capabilities/shopify.capabilities.json +1082 -0
  65. package/integrations/data-defined/capabilities/slack.capabilities.json +363 -0
  66. package/integrations/data-defined/capabilities/stripe.capabilities.json +580 -0
  67. package/integrations/data-defined/capabilities/webflow.capabilities.json +196 -0
  68. package/integrations/data-defined/capabilities/zendesk.capabilities.json +409 -0
  69. package/integrations/data-defined/compile.js +77 -0
  70. package/integrations/data-defined/config-form.js +110 -0
  71. package/integrations/data-defined/data-defined-integration.js +366 -0
  72. package/integrations/data-defined/examples/acme-erp-quickbooks.definition.json +467 -0
  73. package/integrations/data-defined/examples/asana-slack.definition.json +220 -0
  74. package/integrations/data-defined/examples/calendly-salesforce.definition.json +459 -0
  75. package/integrations/data-defined/examples/docusign-salesforce.definition.json +305 -0
  76. package/integrations/data-defined/examples/fathom-hubspot-notion.definition.json +575 -0
  77. package/integrations/data-defined/examples/github-linear.definition.json +316 -0
  78. package/integrations/data-defined/examples/hubspot-slack.definition.json +409 -0
  79. package/integrations/data-defined/examples/klaviyo-hubspot.definition.json +339 -0
  80. package/integrations/data-defined/examples/phone-hubspot.definition.json +637 -0
  81. package/integrations/data-defined/examples/pipedrive-hubspot.definition.json +136 -0
  82. package/integrations/data-defined/examples/pipedrive-phone.definition.json +340 -0
  83. package/integrations/data-defined/examples/records/phone-hubspot.definition-version.json +652 -0
  84. package/integrations/data-defined/examples/records/phone-hubspot.integration.json +86 -0
  85. package/integrations/data-defined/examples/records/phone-hubspot.mappings.json +39 -0
  86. package/integrations/data-defined/examples/records/phone-hubspot.workflow-run.json +62 -0
  87. package/integrations/data-defined/examples/shopify-klaviyo.definition.json +488 -0
  88. package/integrations/data-defined/examples/shopify-netsuite.definition.json +622 -0
  89. package/integrations/data-defined/examples/stripe-quickbooks.definition.json +476 -0
  90. package/integrations/data-defined/examples/webflow-hubspot-slack.definition.json +262 -0
  91. package/integrations/data-defined/examples/zendesk-jira.definition.json +604 -0
  92. package/integrations/data-defined/expressions.js +225 -0
  93. package/integrations/data-defined/index.js +39 -0
  94. package/integrations/data-defined/interpreter.js +89 -0
  95. package/integrations/data-defined/manifests.js +125 -0
  96. package/integrations/data-defined/repositories/definition-version-repository.js +67 -0
  97. package/integrations/data-defined/repositories/workflow-run-repository.js +59 -0
  98. package/integrations/data-defined/router.js +97 -0
  99. package/integrations/data-defined/runtime.js +208 -0
  100. package/integrations/data-defined/schema/capability-manifest.v0.1.schema.json +377 -0
  101. package/integrations/data-defined/schema/installation.v0.1.schema.json +83 -0
  102. package/integrations/data-defined/schema/integration-definition.v0.1.schema.json +848 -0
  103. package/integrations/data-defined/schema/records.md +98 -0
  104. package/integrations/data-defined/schema-check.js +58 -0
  105. package/integrations/data-defined/scripts/validate.js +66 -0
  106. package/integrations/data-defined/steps.js +89 -0
  107. package/integrations/data-defined/transforms.js +91 -0
  108. package/integrations/data-defined/use-cases/definition-use-cases.js +187 -0
  109. package/integrations/data-defined/validate.js +370 -0
  110. package/integrations/data-defined/versioning.js +161 -0
  111. package/integrations/data-defined/webhooks.js +119 -0
  112. package/integrations/index.js +0 -1
  113. package/integrations/integration-base.js +29 -0
  114. package/integrations/integration-router.js +101 -131
  115. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  116. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  117. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  118. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  119. package/integrations/repositories/integration-mapping-repository.js +29 -0
  120. package/integrations/repositories/integration-repository-documentdb.js +3 -35
  121. package/integrations/repositories/integration-repository-interface.js +1 -17
  122. package/integrations/repositories/integration-repository-mongo.js +77 -60
  123. package/integrations/repositories/integration-repository-postgres.js +76 -62
  124. package/integrations/repositories/process-repository-mongo.js +1 -1
  125. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  126. package/integrations/use-cases/create-integration.js +20 -55
  127. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  128. package/integrations/use-cases/index.js +1 -7
  129. package/integrations/utils/map-integration-dto.js +14 -43
  130. package/modules/index.js +0 -14
  131. package/modules/module-factory.js +0 -12
  132. package/modules/module.js +2 -53
  133. package/modules/repositories/module-repository-documentdb.js +6 -51
  134. package/modules/repositories/module-repository-interface.js +2 -19
  135. package/modules/repositories/module-repository-mongo.js +74 -67
  136. package/modules/repositories/module-repository-postgres.js +74 -66
  137. package/modules/repositories/module-repository.js +1 -2
  138. package/modules/requester/requester.js +5 -35
  139. package/modules/use-cases/get-entities-for-user.js +13 -26
  140. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  141. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  142. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  143. package/modules/use-cases/get-module.js +4 -10
  144. package/modules/use-cases/process-authorization-callback.js +19 -53
  145. package/modules/use-cases/refresh-entity-options.js +4 -10
  146. package/modules/use-cases/test-module-auth.js +4 -10
  147. package/package.json +6 -6
  148. package/prisma-mongodb/schema.prisma +50 -86
  149. package/prisma-postgresql/migrations/20261018000000_add_data_defined_integrations/migration.sql +66 -0
  150. package/prisma-postgresql/schema.prisma +48 -81
  151. package/queues/index.js +0 -25
  152. package/types/index.d.ts +0 -2
  153. package/user/repositories/user-repository-documentdb.js +2 -27
  154. package/user/repositories/user-repository-interface.js +0 -18
  155. package/user/repositories/user-repository-mongo.js +0 -24
  156. package/user/repositories/user-repository-postgres.js +0 -18
  157. package/user/use-cases/authenticate-user.js +1 -7
  158. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  159. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  160. package/user/user.js +1 -64
  161. package/credential/use-cases/credential-operations-for-user.js +0 -202
  162. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  163. package/database/organization-where.js +0 -18
  164. package/database/unique-violation.js +0 -25
  165. package/extensions/bootstrap-runner.js +0 -43
  166. package/extensions/extension-loader.js +0 -187
  167. package/extensions/index.js +0 -41
  168. package/extensions/initialize-app.js +0 -123
  169. package/extensions/route-mounter.js +0 -103
  170. package/extensions/schema-composer.js +0 -188
  171. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -216
  172. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  173. package/integrations/repositories/integration-not-found.js +0 -14
  174. package/integrations/schedule-config.js +0 -80
  175. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  176. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  177. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  178. package/management-api/create-management-api-router.js +0 -49
  179. package/management-api/error-response.js +0 -116
  180. package/management-api/management-api-config.js +0 -151
  181. package/management-api/meta-router.js +0 -76
  182. package/management-api/openapi/build-openapi.js +0 -144
  183. package/management-api/openapi/components.js +0 -415
  184. package/management-api/openapi/v1-operations.js +0 -83
  185. package/management-api/openapi/v2-operations.js +0 -264
  186. package/management-api/route-registry.js +0 -411
  187. package/management-api/test-support/http-client.js +0 -39
  188. package/management-api/use-cases/get-management-api-meta.js +0 -57
  189. package/management-api/v1-deprecation.js +0 -120
  190. package/management-api/v2/create-v2-router.js +0 -85
  191. package/management-api/v2/dependencies.js +0 -281
  192. package/management-api/v2/dto.js +0 -143
  193. package/management-api/v2/handlers/authorization.js +0 -94
  194. package/management-api/v2/handlers/credentials.js +0 -33
  195. package/management-api/v2/handlers/entities.js +0 -77
  196. package/management-api/v2/handlers/integrations.js +0 -128
  197. package/management-api/v2/handlers/proxy.js +0 -34
  198. package/management-api/v2/request.js +0 -85
  199. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  200. package/modules/domain/authorization-session.js +0 -144
  201. package/modules/entity-scope.js +0 -15
  202. package/modules/entity-type-registry.js +0 -311
  203. package/modules/proxy/proxy-bases.js +0 -169
  204. package/modules/proxy/proxy-errors.js +0 -31
  205. package/modules/proxy/proxy-path.js +0 -183
  206. package/modules/proxy/proxy-policy.js +0 -277
  207. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  208. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  209. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  210. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  211. package/modules/repositories/entity-not-found.js +0 -18
  212. package/modules/requester/proxy-request.js +0 -245
  213. package/modules/scope-config.js +0 -192
  214. package/modules/scope-policy.js +0 -112
  215. package/modules/scope-resolution.js +0 -288
  216. package/modules/scope-visibility.js +0 -247
  217. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  218. package/modules/tests/doubles/ownership-matching.js +0 -18
  219. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  220. package/modules/use-cases/create-entity-from-credential.js +0 -113
  221. package/modules/use-cases/entity-operations-for-user.js +0 -162
  222. package/modules/use-cases/entity-types.js +0 -139
  223. package/modules/use-cases/execute-entity-proxy-request.js +0 -331
  224. package/modules/use-cases/get-authorization-requirements.js +0 -100
  225. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  226. package/modules/use-cases/manage-access.js +0 -21
  227. package/modules/use-cases/multi-step-authorization.js +0 -329
  228. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  229. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  230. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  231. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  232. package/providers/index.js +0 -23
  233. package/providers/resolve-provider.js +0 -93
  234. package/providers/runtime-detection.js +0 -124
  235. package/queues/providers/index.js +0 -11
  236. package/queues/providers/netlify-background-provider.js +0 -150
  237. package/queues/providers/qstash-queue-provider.js +0 -219
  238. package/queues/providers/sqs-queue-provider.js +0 -70
  239. package/queues/queue-client-interface.js +0 -55
  240. package/queues/queue-message-auth.js +0 -193
  241. package/queues/queue-provider-factory.js +0 -101
  242. package/queues/queue-provider.js +0 -65
  243. package/queues/sqs-queue-client.js +0 -81
  244. package/types/modules/index.d.ts +0 -214
  245. package/types/user/index.d.ts +0 -134
  246. package/user/adopter-jwt-config.js +0 -177
  247. package/user/adopter-jwt-errors.js +0 -59
  248. package/user/adopter-token-verifier.js +0 -122
  249. package/user/app-user-helpers.js +0 -97
  250. package/user/create-user-authentication.js +0 -64
  251. package/user/membership.js +0 -51
  252. package/user/repositories/organization-membership-record.js +0 -19
  253. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  254. package/user/repositories/organization-membership-repository-factory.js +0 -42
  255. package/user/repositories/organization-membership-repository-interface.js +0 -57
  256. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  257. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  258. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  259. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  260. package/user/use-cases/ensure-organization-membership.js +0 -92
  261. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -1,4 +1,5 @@
1
1
  const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
2
3
  const {
3
4
  toObjectId,
4
5
  fromObjectId,
@@ -180,6 +181,55 @@ class IntegrationMappingRepositoryDocumentDB extends IntegrationMappingRepositor
180
181
  return this._mapMapping(decryptedMapping);
181
182
  }
182
183
 
184
+ async createMapping(integrationId, sourceId, mapping) {
185
+ const now = new Date();
186
+ const encryptedDocument = await this.encryptionService.encryptFields(
187
+ 'IntegrationMapping',
188
+ {
189
+ integrationId,
190
+ sourceId:
191
+ sourceId === null || sourceId === undefined
192
+ ? null
193
+ : String(sourceId),
194
+ mapping,
195
+ createdAt: now,
196
+ updatedAt: now,
197
+ }
198
+ );
199
+
200
+ let insertedId;
201
+ try {
202
+ insertedId = await insertOne(
203
+ this.prisma,
204
+ 'IntegrationMapping',
205
+ encryptedDocument
206
+ );
207
+ } catch (error) {
208
+ if (error.code === 11000) {
209
+ throw new MappingAlreadyExistsError(integrationId, sourceId, {
210
+ cause: error,
211
+ });
212
+ }
213
+ throw error;
214
+ }
215
+
216
+ const created = await findOne(this.prisma, 'IntegrationMapping', {
217
+ _id: insertedId,
218
+ });
219
+ if (!created) {
220
+ throw new Error(
221
+ 'Failed to create mapping: Document not found after insert. ' +
222
+ 'This indicates a database consistency issue.'
223
+ );
224
+ }
225
+ return this._mapMapping(
226
+ await this.encryptionService.decryptFields(
227
+ 'IntegrationMapping',
228
+ created
229
+ )
230
+ );
231
+ }
232
+
183
233
  async findMappingsByIntegration(integrationId) {
184
234
  const filter = {};
185
235
  if (integrationId) filter.integrationId = integrationId;
@@ -39,6 +39,21 @@ class IntegrationMappingRepositoryInterface {
39
39
  throw new Error('Method upsertMapping must be implemented by subclass');
40
40
  }
41
41
 
42
+ /**
43
+ * Insert a mapping, failing if one already exists for the
44
+ * (integrationId, sourceId) pair. Atomic: usable as a claim/lock row.
45
+ *
46
+ * @param {string|number} integrationId - The integration ID
47
+ * @param {string} sourceId - The source ID for lookup
48
+ * @param {Object} mapping - The mapping data
49
+ * @returns {Promise<Object>} The created mapping document
50
+ * @throws {MappingAlreadyExistsError} If the pair already exists
51
+ * @abstract
52
+ */
53
+ async createMapping(integrationId, sourceId, mapping) {
54
+ throw new Error('createMapping must be implemented by subclass');
55
+ }
56
+
42
57
  /**
43
58
  * Find all mappings for an integration
44
59
  *
@@ -1,4 +1,5 @@
1
1
  const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
2
3
  const {
3
4
  assertMappingWrittenUnencrypted,
4
5
  decryptQueriedMappings,
@@ -93,6 +94,34 @@ class IntegrationMappingRepositoryMongo extends IntegrationMappingRepositoryInte
93
94
  });
94
95
  }
95
96
 
97
+ /**
98
+ * Insert a mapping; fails if (integrationId, sourceId) already exists
99
+ *
100
+ * @param {string} integrationId - The integration ID
101
+ * @param {string} sourceId - The source ID
102
+ * @param {Object} mapping - The mapping data
103
+ * @returns {Promise<Object>} The created mapping document
104
+ * @throws {MappingAlreadyExistsError} If the pair already exists
105
+ */
106
+ async createMapping(integrationId, sourceId, mapping) {
107
+ try {
108
+ return await this.prisma.integrationMapping.create({
109
+ data: {
110
+ integrationId,
111
+ sourceId: this._toString(sourceId),
112
+ mapping,
113
+ },
114
+ });
115
+ } catch (error) {
116
+ if (error.code === 'P2002') {
117
+ throw new MappingAlreadyExistsError(integrationId, sourceId, {
118
+ cause: error,
119
+ });
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+
96
125
  /**
97
126
  * Find all mappings for an integration
98
127
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -1,4 +1,5 @@
1
1
  const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
2
3
  const {
3
4
  assertMappingWrittenUnencrypted,
4
5
  decryptQueriedMappings,
@@ -150,6 +151,35 @@ class IntegrationMappingRepositoryPostgres extends IntegrationMappingRepositoryI
150
151
  return this._convertMappingIds(result);
151
152
  }
152
153
 
154
+ /**
155
+ * Insert a mapping; fails if (integrationId, sourceId) already exists
156
+ *
157
+ * @param {string} integrationId - The integration ID
158
+ * @param {string} sourceId - The source ID
159
+ * @param {Object} mapping - The mapping data
160
+ * @returns {Promise<Object>} The created mapping document
161
+ * @throws {MappingAlreadyExistsError} If the pair already exists
162
+ */
163
+ async createMapping(integrationId, sourceId, mapping) {
164
+ try {
165
+ const created = await this.prisma.integrationMapping.create({
166
+ data: {
167
+ integrationId: this._stringToInt(integrationId),
168
+ sourceId: this._toString(sourceId),
169
+ mapping,
170
+ },
171
+ });
172
+ return this._convertMappingIds(created);
173
+ } catch (error) {
174
+ if (error.code === 'P2002') {
175
+ throw new MappingAlreadyExistsError(integrationId, sourceId, {
176
+ cause: error,
177
+ });
178
+ }
179
+ throw error;
180
+ }
181
+ }
182
+
153
183
  /**
154
184
  * Find all mappings for an integration
155
185
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -1,4 +1,5 @@
1
1
  const { prisma } = require('../../database/prisma');
2
+ const { MappingAlreadyExistsError } = require('../../errors');
2
3
  const {
3
4
  IntegrationMappingRepositoryInterface,
4
5
  } = require('./integration-mapping-repository-interface');
@@ -69,6 +70,34 @@ class IntegrationMappingRepository extends IntegrationMappingRepositoryInterface
69
70
  });
70
71
  }
71
72
 
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
+
72
101
  /**
73
102
  * Find all mappings for an integration
74
103
  * Replaces: IntegrationMapping.find({ integration: integrationId })
@@ -14,7 +14,6 @@ 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');
18
17
 
19
18
  class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
20
19
  constructor() {
@@ -29,28 +28,6 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
29
28
  return records.map((doc) => this._mapIntegration(doc));
30
29
  }
31
30
 
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
31
  async findIntegrations({ type, status } = {}) {
55
32
  const filter = {};
56
33
  if (type) {
@@ -91,11 +68,11 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
91
68
  async findIntegrationById(id) {
92
69
  const objectId = toObjectId(id);
93
70
  if (!objectId) {
94
- throw integrationNotFoundError(id);
71
+ throw new Error(`Integration with id ${id} not found`);
95
72
  }
96
73
  const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
97
74
  if (!doc) {
98
- throw integrationNotFoundError(id);
75
+ throw new Error(`Integration with id ${id} not found`);
99
76
  }
100
77
  return this._mapIntegration(doc);
101
78
  }
@@ -155,18 +132,10 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
155
132
  return true;
156
133
  }
157
134
 
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 = {}) {
135
+ async createIntegration(entities, userId, config) {
166
136
  const now = new Date();
167
137
  const document = {
168
138
  userId: toObjectId(userId) || null,
169
- organizationId: toObjectId(tenancy.organizationId) || null,
170
139
  config,
171
140
  version: '0.0.0',
172
141
  status: 'IN_CREATION',
@@ -329,7 +298,6 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
329
298
  id: fromObjectId(doc?._id),
330
299
  entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
331
300
  userId: fromObjectId(doc?.userId),
332
- organizationId: fromObjectId(doc?.organizationId) ?? null,
333
301
  config: doc?.config ?? null,
334
302
  version: doc?.version ?? null,
335
303
  status: doc?.status ?? null,
@@ -10,20 +10,6 @@
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
-
27
13
  /**
28
14
  * Find all integrations for a user
29
15
  *
@@ -137,12 +123,10 @@ class IntegrationRepositoryInterface {
137
123
  * @param {Array<string|number>} entities - Array of entity IDs
138
124
  * @param {string|number} userId - User ID
139
125
  * @param {Object} config - Integration configuration
140
- * @param {{organizationId?: (string|number|null)}} [tenancy] - the organization the
141
- * integration is created in (ADR-056 §3.6)
142
126
  * @returns {Promise<Object>} Created integration object
143
127
  * @abstract
144
128
  */
145
- async createIntegration(entities, userId, config, tenancy) {
129
+ async createIntegration(entities, userId, config) {
146
130
  throw new Error('Method createIntegration must be implemented by subclass');
147
131
  }
148
132
 
@@ -1,10 +1,8 @@
1
1
  const { prisma } = require('../../database/prisma');
2
- const { organizationWhere } = require('../../database/organization-where');
3
2
  const {
4
3
  IntegrationRepositoryInterface,
5
4
  } = require('./integration-repository-interface');
6
5
  const { validateConfigPatch } = require('./config-patch-shared');
7
- const { integrationNotFoundError } = require('./integration-not-found');
8
6
 
9
7
  /**
10
8
  * MongoDB Integration Repository Adapter
@@ -27,25 +25,6 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
27
25
  this.prisma = prisma;
28
26
  }
29
27
 
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
-
49
28
  /**
50
29
  * Find all integrations for a user
51
30
  * Replaces: IntegrationModel.find({ user: userId }).populate('entities')
@@ -62,32 +41,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
62
41
  });
63
42
 
64
43
  // Map to domain objects (maintains same API)
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)));
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
+ }));
91
54
  }
92
55
 
93
56
  /**
@@ -114,7 +77,16 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
114
77
  },
115
78
  });
116
79
 
117
- return integrations.map((integration) => (this._mapIntegration(integration)));
80
+ return integrations.map((integration) => ({
81
+ id: integration.id,
82
+ entitiesIds: integration.entities.map((e) => e.id),
83
+ userId: integration.userId,
84
+ config: integration.config,
85
+ version: integration.version,
86
+ status: integration.status,
87
+ messages: integration.messages,
88
+ createdAt: integration.createdAt,
89
+ }));
118
90
  }
119
91
 
120
92
  /**
@@ -193,7 +165,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
193
165
  throw new Error(`Integration with name ${name} not found`);
194
166
  }
195
167
 
196
- return this._mapIntegration(integration);
168
+ return {
169
+ id: integration.id,
170
+ entitiesIds: integration.entities.map((e) => e.id),
171
+ userId: integration.userId,
172
+ config: integration.config,
173
+ version: integration.version,
174
+ status: integration.status,
175
+ messages: integration.messages,
176
+ };
197
177
  }
198
178
 
199
179
  /**
@@ -212,10 +192,18 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
212
192
  });
213
193
 
214
194
  if (!integration) {
215
- throw integrationNotFoundError(id);
195
+ throw new Error(`Integration with id ${id} not found`);
216
196
  }
217
197
 
218
- return this._mapIntegration(integration);
198
+ return {
199
+ id: integration.id,
200
+ entitiesIds: integration.entities.map((e) => e.id),
201
+ userId: integration.userId,
202
+ config: integration.config,
203
+ version: integration.version,
204
+ status: integration.status,
205
+ messages: integration.messages,
206
+ };
219
207
  }
220
208
 
221
209
  /**
@@ -302,7 +290,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
302
290
  },
303
291
  });
304
292
 
305
- return integrations.map((integration) => (this._mapIntegration(integration)));
293
+ return integrations.map((integration) => ({
294
+ id: integration.id,
295
+ entitiesIds: integration.entities.map((e) => e.id),
296
+ userId: integration.userId,
297
+ config: integration.config,
298
+ version: integration.version,
299
+ status: integration.status,
300
+ messages: integration.messages,
301
+ }));
306
302
  }
307
303
 
308
304
  /**
@@ -314,16 +310,13 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
314
310
  * @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
315
311
  * @param {string} userId - User ID (MongoDB ObjectId)
316
312
  * @param {Object} config - Integration configuration
317
- * @param {{organizationId?: (string|null)}} [tenancy] - the organization the
318
- * integration is created in (ADR-056 §3.6)
319
313
  * @returns {Promise<Object>} Created integration object
320
314
  */
321
- async createIntegration(entities, userId, config, tenancy = {}) {
315
+ async createIntegration(entities, userId, config) {
322
316
  const data = {
323
317
  config,
324
318
  version: '0.0.0',
325
319
  userId: userId,
326
- organizationId: tenancy.organizationId ?? null,
327
320
  entityIds: entities,
328
321
  };
329
322
 
@@ -334,7 +327,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
334
327
  },
335
328
  });
336
329
 
337
- return this._mapIntegration(integration);
330
+ return {
331
+ id: integration.id,
332
+ entitiesIds: integration.entities.map((e) => e.id),
333
+ userId: integration.userId,
334
+ config: integration.config,
335
+ version: integration.version,
336
+ status: integration.status,
337
+ messages: integration.messages,
338
+ };
338
339
  }
339
340
 
340
341
  /**
@@ -356,7 +357,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
356
357
  return null;
357
358
  }
358
359
 
359
- return this._mapIntegration(integration);
360
+ return {
361
+ id: integration.id,
362
+ entitiesIds: integration.entities.map((e) => e.id),
363
+ userId: integration.userId,
364
+ config: integration.config,
365
+ version: integration.version,
366
+ status: integration.status,
367
+ messages: integration.messages,
368
+ };
360
369
  }
361
370
 
362
371
  /**
@@ -380,7 +389,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
380
389
  },
381
390
  });
382
391
 
383
- return this._mapIntegration(integration);
392
+ return {
393
+ id: integration.id,
394
+ entitiesIds: integration.entities.map((e) => e.id),
395
+ userId: integration.userId,
396
+ config: integration.config,
397
+ version: integration.version,
398
+ status: integration.status,
399
+ messages: integration.messages,
400
+ };
384
401
  }
385
402
 
386
403
  /**
@@ -402,7 +419,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
402
419
  for (const [key, value] of Object.entries(patch)) {
403
420
  $set[`config.${key}`] = value;
404
421
  }
405
- $set.updatedAt = new Date();
422
+ $set.updatedAt = { $date: new Date().toISOString() };
406
423
 
407
424
  const result = await this.prisma.$runCommandRaw({
408
425
  findAndModify: 'Integration',