@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
@@ -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,29 +28,7 @@ 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
- async findIntegrations({ type, status } = {}) {
31
+ async findIntegrations({ type, status, limit, after } = {}) {
55
32
  const filter = {};
56
33
  if (type) {
57
34
  filter['config.type'] = type;
@@ -59,7 +36,11 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
59
36
  if (status) {
60
37
  filter.status = status;
61
38
  }
62
- const records = await findMany(this.prisma, 'Integration', filter);
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 } : {});
63
44
  return records.map((doc) => this._mapIntegration(doc));
64
45
  }
65
46
 
@@ -91,11 +72,11 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
91
72
  async findIntegrationById(id) {
92
73
  const objectId = toObjectId(id);
93
74
  if (!objectId) {
94
- throw integrationNotFoundError(id);
75
+ throw new Error(`Integration with id ${id} not found`);
95
76
  }
96
77
  const doc = await findOne(this.prisma, 'Integration', { _id: objectId });
97
78
  if (!doc) {
98
- throw integrationNotFoundError(id);
79
+ throw new Error(`Integration with id ${id} not found`);
99
80
  }
100
81
  return this._mapIntegration(doc);
101
82
  }
@@ -155,18 +136,10 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
155
136
  return true;
156
137
  }
157
138
 
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 = {}) {
139
+ async createIntegration(entities, userId, config) {
166
140
  const now = new Date();
167
141
  const document = {
168
142
  userId: toObjectId(userId) || null,
169
- organizationId: toObjectId(tenancy.organizationId) || null,
170
143
  config,
171
144
  version: '0.0.0',
172
145
  status: 'IN_CREATION',
@@ -329,7 +302,6 @@ class IntegrationRepositoryDocumentDB extends IntegrationRepositoryInterface {
329
302
  id: fromObjectId(doc?._id),
330
303
  entitiesIds: (doc?.entityIds || []).map((value) => fromObjectId(value)),
331
304
  userId: fromObjectId(doc?.userId),
332
- organizationId: fromObjectId(doc?.organizationId) ?? null,
333
305
  config: doc?.config ?? null,
334
306
  version: doc?.version ?? null,
335
307
  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
  *
@@ -42,6 +28,9 @@ class IntegrationRepositoryInterface {
42
28
  * @param {Object} [filter={}]
43
29
  * @param {string} [filter.type] - Integration type (config.type)
44
30
  * @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)
45
34
  * @returns {Promise<Array>} Array of integration objects (possibly empty)
46
35
  * @abstract
47
36
  */
@@ -137,12 +126,10 @@ class IntegrationRepositoryInterface {
137
126
  * @param {Array<string|number>} entities - Array of entity IDs
138
127
  * @param {string|number} userId - User ID
139
128
  * @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
129
  * @returns {Promise<Object>} Created integration object
143
130
  * @abstract
144
131
  */
145
- async createIntegration(entities, userId, config, tenancy) {
132
+ async createIntegration(entities, userId, config) {
146
133
  throw new Error('Method createIntegration must be implemented by subclass');
147
134
  }
148
135
 
@@ -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
  /**
@@ -98,23 +61,47 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
98
61
  * @param {string} [filter.status] - Integration status
99
62
  * @returns {Promise<Array>} Array of integration objects (possibly empty)
100
63
  */
101
- async findIntegrations({ type, status } = {}) {
64
+ async findIntegrations({ type, status, limit, after } = {}) {
102
65
  const where = {};
103
- if (type) {
104
- where.config = { path: ['type'], equals: type };
105
- }
106
66
  if (status) {
107
67
  where.status = status;
108
68
  }
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
+ }
109
86
 
110
87
  const integrations = await this.prisma.integration.findMany({
111
88
  where,
112
89
  include: {
113
90
  entities: true,
114
91
  },
92
+ ...(limit ? { orderBy: { id: 'asc' }, take: limit } : {}),
115
93
  });
116
94
 
117
- return integrations.map((integration) => (this._mapIntegration(integration)));
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
+ }));
118
105
  }
119
106
 
120
107
  /**
@@ -193,7 +180,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
193
180
  throw new Error(`Integration with name ${name} not found`);
194
181
  }
195
182
 
196
- return this._mapIntegration(integration);
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
+ };
197
192
  }
198
193
 
199
194
  /**
@@ -212,10 +207,18 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
212
207
  });
213
208
 
214
209
  if (!integration) {
215
- throw integrationNotFoundError(id);
210
+ throw new Error(`Integration with id ${id} not found`);
216
211
  }
217
212
 
218
- return this._mapIntegration(integration);
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
+ };
219
222
  }
220
223
 
221
224
  /**
@@ -302,7 +305,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
302
305
  },
303
306
  });
304
307
 
305
- return integrations.map((integration) => (this._mapIntegration(integration)));
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
+ }));
306
317
  }
307
318
 
308
319
  /**
@@ -314,16 +325,13 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
314
325
  * @param {Array<string>} entities - Array of entity IDs (MongoDB ObjectIds)
315
326
  * @param {string} userId - User ID (MongoDB ObjectId)
316
327
  * @param {Object} config - Integration configuration
317
- * @param {{organizationId?: (string|null)}} [tenancy] - the organization the
318
- * integration is created in (ADR-056 §3.6)
319
328
  * @returns {Promise<Object>} Created integration object
320
329
  */
321
- async createIntegration(entities, userId, config, tenancy = {}) {
330
+ async createIntegration(entities, userId, config) {
322
331
  const data = {
323
332
  config,
324
333
  version: '0.0.0',
325
334
  userId: userId,
326
- organizationId: tenancy.organizationId ?? null,
327
335
  entityIds: entities,
328
336
  };
329
337
 
@@ -334,7 +342,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
334
342
  },
335
343
  });
336
344
 
337
- return this._mapIntegration(integration);
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
+ };
338
354
  }
339
355
 
340
356
  /**
@@ -356,7 +372,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
356
372
  return null;
357
373
  }
358
374
 
359
- return this._mapIntegration(integration);
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
+ };
360
384
  }
361
385
 
362
386
  /**
@@ -380,7 +404,15 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
380
404
  },
381
405
  });
382
406
 
383
- return this._mapIntegration(integration);
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
+ };
384
416
  }
385
417
 
386
418
  /**
@@ -402,7 +434,7 @@ class IntegrationRepositoryMongo extends IntegrationRepositoryInterface {
402
434
  for (const [key, value] of Object.entries(patch)) {
403
435
  $set[`config.${key}`] = value;
404
436
  }
405
- $set.updatedAt = new Date();
437
+ $set.updatedAt = { $date: new Date().toISOString() };
406
438
 
407
439
  const result = await this.prisma.$runCommandRaw({
408
440
  findAndModify: 'Integration',
@@ -3,7 +3,6 @@ const {
3
3
  IntegrationRepositoryInterface,
4
4
  } = require('./integration-repository-interface');
5
5
  const { validateConfigPatch } = require('./config-patch-shared');
6
- const { integrationNotFoundError } = require('./integration-not-found');
7
6
  const { strictIntId } = require('./report-id');
8
7
 
9
8
  /**
@@ -52,7 +51,6 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
52
51
  ...integration,
53
52
  id: integration.id?.toString(),
54
53
  userId: integration.userId?.toString(),
55
- organizationId: integration.organizationId?.toString() ?? null,
56
54
  entities: integration.entities?.map(e => ({
57
55
  ...e,
58
56
  id: e.id?.toString(),
@@ -62,25 +60,6 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
62
60
  };
63
61
  }
64
62
 
65
- /**
66
- * The one place a converted integration row becomes the record the
67
- * application sees (string ids, ids of its entities, its organization).
68
- * @private
69
- */
70
- _mapIntegration(converted) {
71
- return {
72
- id: converted.id,
73
- entitiesIds: converted.entities.map((e) => e.id),
74
- userId: converted.userId,
75
- organizationId: converted.organizationId,
76
- config: converted.config,
77
- version: converted.version,
78
- status: converted.status,
79
- messages: converted.messages,
80
- createdAt: converted.createdAt,
81
- };
82
- }
83
-
84
63
  /**
85
64
  * Find all integrations for a user
86
65
  *
@@ -99,35 +78,16 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
99
78
  // Map to domain objects with string IDs
100
79
  return integrations.map((integration) => {
101
80
  const converted = this._convertIntegrationIds(integration);
102
- return this._mapIntegration(converted);
103
- });
104
- }
105
-
106
- /**
107
- * Find integrations matching any of the owner/organization pairs, in one
108
- * query. `organizationId: null` matches rows with no organization. The
109
- * caller (a use case) decides which pairs; this only runs them.
110
- *
111
- * @param {Array<{userId: string, organizationId: (string|null)}>} clauses
112
- * @returns {Promise<Array>} Array of integration objects with string IDs
113
- */
114
- async findIntegrationsByOwnership(clauses) {
115
- if (!Array.isArray(clauses) || clauses.length === 0) return [];
116
- const integrations = await this.prisma.integration.findMany({
117
- where: {
118
- OR: clauses.map(({ userId, organizationId }) => ({
119
- userId: this._convertId(userId),
120
- organizationId: this._convertId(organizationId),
121
- })),
122
- },
123
- include: {
124
- entities: true,
125
- },
126
- });
127
-
128
- return integrations.map((integration) => {
129
- const converted = this._convertIntegrationIds(integration);
130
- return this._mapIntegration(converted);
81
+ return {
82
+ id: converted.id,
83
+ entitiesIds: converted.entities.map((e) => e.id),
84
+ userId: converted.userId,
85
+ config: converted.config,
86
+ version: converted.version,
87
+ status: converted.status,
88
+ messages: converted.messages,
89
+ createdAt: converted.createdAt,
90
+ };
131
91
  });
132
92
  }
133
93
 
@@ -139,7 +99,7 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
139
99
  * @param {string} [filter.status] - Integration status
140
100
  * @returns {Promise<Array>} Array of integration objects (possibly empty)
141
101
  */
142
- async findIntegrations({ type, status } = {}) {
102
+ async findIntegrations({ type, status, limit, after } = {}) {
143
103
  const where = {};
144
104
  if (type) {
145
105
  where.config = { path: ['type'], equals: type };
@@ -147,17 +107,30 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
147
107
  if (status) {
148
108
  where.status = status;
149
109
  }
110
+ if (limit && after !== undefined && after !== null) {
111
+ where.id = { gt: this._convertId(after) };
112
+ }
150
113
 
151
114
  const integrations = await this.prisma.integration.findMany({
152
115
  where,
153
116
  include: {
154
117
  entities: true,
155
118
  },
119
+ ...(limit ? { orderBy: { id: 'asc' }, take: limit } : {}),
156
120
  });
157
121
 
158
122
  return integrations.map((integration) => {
159
123
  const converted = this._convertIntegrationIds(integration);
160
- return this._mapIntegration(converted);
124
+ return {
125
+ id: converted.id,
126
+ entitiesIds: converted.entities.map((e) => e.id),
127
+ userId: converted.userId,
128
+ config: converted.config,
129
+ version: converted.version,
130
+ status: converted.status,
131
+ messages: converted.messages,
132
+ createdAt: converted.createdAt,
133
+ };
161
134
  });
162
135
  }
163
136
 
@@ -240,7 +213,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
240
213
  }
241
214
 
242
215
  const converted = this._convertIntegrationIds(integration);
243
- return this._mapIntegration(converted);
216
+ return {
217
+ id: converted.id,
218
+ entitiesIds: converted.entities.map((e) => e.id),
219
+ userId: converted.userId,
220
+ config: converted.config,
221
+ version: converted.version,
222
+ status: converted.status,
223
+ messages: converted.messages,
224
+ };
244
225
  }
245
226
 
246
227
  /**
@@ -259,11 +240,19 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
259
240
  });
260
241
 
261
242
  if (!integration) {
262
- throw integrationNotFoundError(id);
243
+ throw new Error(`Integration with id ${id} not found`);
263
244
  }
264
245
 
265
246
  const converted = this._convertIntegrationIds(integration);
266
- return this._mapIntegration(converted);
247
+ return {
248
+ id: converted.id,
249
+ entitiesIds: converted.entities.map((e) => e.id),
250
+ userId: converted.userId,
251
+ config: converted.config,
252
+ version: converted.version,
253
+ status: converted.status,
254
+ messages: converted.messages,
255
+ };
267
256
  }
268
257
 
269
258
  /**
@@ -343,15 +332,12 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
343
332
  * @param {Array<string>} entities - Array of entity IDs (strings from application layer)
344
333
  * @param {string} userId - User ID (string from application layer)
345
334
  * @param {Object} config - Integration configuration
346
- * @param {{organizationId?: (string|null)}} [tenancy] - the organization the
347
- * integration is created in (ADR-056 §3.6)
348
335
  * @returns {Promise<Object>} Created integration object with string IDs
349
336
  */
350
- async createIntegration(entities, userId, config, tenancy = {}) {
337
+ async createIntegration(entities, userId, config) {
351
338
  const data = {
352
339
  config,
353
340
  version: '0.0.0',
354
- organizationId: this._convertId(tenancy.organizationId ?? null),
355
341
  };
356
342
 
357
343
  // PostgreSQL: use nested relations with ID conversion
@@ -372,7 +358,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
372
358
  });
373
359
 
374
360
  const converted = this._convertIntegrationIds(integration);
375
- return this._mapIntegration(converted);
361
+ return {
362
+ id: converted.id,
363
+ entitiesIds: converted.entities.map((e) => e.id),
364
+ userId: converted.userId,
365
+ config: converted.config,
366
+ version: converted.version,
367
+ status: converted.status,
368
+ messages: converted.messages,
369
+ };
376
370
  }
377
371
 
378
372
  /**
@@ -395,7 +389,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
395
389
  }
396
390
 
397
391
  const converted = this._convertIntegrationIds(integration);
398
- return this._mapIntegration(converted);
392
+ return {
393
+ id: converted.id,
394
+ entitiesIds: converted.entities.map((e) => e.id),
395
+ userId: converted.userId,
396
+ config: converted.config,
397
+ version: converted.version,
398
+ status: converted.status,
399
+ messages: converted.messages,
400
+ };
399
401
  }
400
402
 
401
403
  /**
@@ -420,7 +422,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
420
422
  });
421
423
 
422
424
  const converted = this._convertIntegrationIds(integration);
423
- return this._mapIntegration(converted);
425
+ return {
426
+ id: converted.id,
427
+ entitiesIds: converted.entities.map((e) => e.id),
428
+ userId: converted.userId,
429
+ config: converted.config,
430
+ version: converted.version,
431
+ status: converted.status,
432
+ messages: converted.messages,
433
+ };
424
434
  }
425
435
 
426
436
  /**
@@ -470,7 +480,15 @@ class IntegrationRepositoryPostgres extends IntegrationRepositoryInterface {
470
480
 
471
481
  return integrations.map((integration) => {
472
482
  const converted = this._convertIntegrationIds(integration);
473
- return this._mapIntegration(converted);
483
+ return {
484
+ id: converted.id,
485
+ entitiesIds: converted.entities.map((e) => e.id),
486
+ userId: converted.userId,
487
+ config: converted.config,
488
+ version: converted.version,
489
+ status: converted.status,
490
+ messages: converted.messages,
491
+ };
474
492
  });
475
493
  }
476
494
  }