@friggframework/core 2.0.0--canary.686.df38e8e.0 → 2.0.0--canary.710.9045bbc.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 (217) hide show
  1. package/CHANGELOG.md +0 -16
  2. package/CLAUDE.md +41 -29
  3. package/README.md +0 -105
  4. package/application/commands/README.md +17 -11
  5. package/application/commands/entity-commands.js +18 -60
  6. package/application/commands/integration-commands.js +73 -20
  7. package/application/commands/scheduler-commands.js +7 -11
  8. package/application/commands/user-commands.js +5 -17
  9. package/application/index.js +2 -6
  10. package/core/Worker.js +7 -31
  11. package/core/create-handler.js +0 -9
  12. package/credential/repositories/credential-repository-documentdb.js +0 -34
  13. package/credential/repositories/credential-repository-interface.js +0 -15
  14. package/credential/repositories/credential-repository-mongo.js +0 -23
  15. package/credential/repositories/credential-repository-postgres.js +0 -28
  16. package/database/config.js +24 -48
  17. package/database/documentdb-encryption-service.js +22 -9
  18. package/database/documentdb-utils.js +0 -2
  19. package/database/encryption/README.md +17 -81
  20. package/database/encryption/documentdb-encryption-service.md +1 -78
  21. package/database/encryption/encryption-schema-registry.js +46 -123
  22. package/database/encryption/field-encryption-service.js +5 -69
  23. package/database/encryption/integration-mapping-encryption.js +1 -1
  24. package/database/prisma.js +38 -15
  25. package/database/use-cases/check-encryption-health-use-case.js +52 -43
  26. package/errors/index.js +4 -0
  27. package/errors/mapping-already-exists-error.js +14 -0
  28. package/generated/prisma-mongodb/edge.js +4 -43
  29. package/generated/prisma-mongodb/index-browser.js +1 -40
  30. package/generated/prisma-mongodb/index.d.ts +111 -4739
  31. package/generated/prisma-mongodb/index.js +4 -43
  32. package/generated/prisma-mongodb/package.json +1 -1
  33. package/generated/prisma-mongodb/schema.prisma +0 -92
  34. package/generated/prisma-mongodb/wasm.js +4 -43
  35. package/generated/prisma-postgresql/edge.js +4 -43
  36. package/generated/prisma-postgresql/index-browser.js +1 -40
  37. package/generated/prisma-postgresql/index.d.ts +33 -4966
  38. package/generated/prisma-postgresql/index.js +4 -43
  39. package/generated/prisma-postgresql/package.json +1 -1
  40. package/generated/prisma-postgresql/schema.prisma +0 -87
  41. package/generated/prisma-postgresql/wasm.js +4 -43
  42. package/handlers/WEBHOOKS.md +29 -0
  43. package/handlers/app-definition-loader.js +19 -121
  44. package/handlers/app-handler-helpers.js +3 -25
  45. package/handlers/backend-utils.js +25 -31
  46. package/handlers/middleware/admin-auth.js +1 -14
  47. package/handlers/routers/auth.js +10 -46
  48. package/handlers/routers/health.js +4 -13
  49. package/handlers/routers/integration-defined-routers.js +92 -144
  50. package/handlers/routers/integration-webhook-routers.js +64 -102
  51. package/handlers/workers/integration-defined-workers.js +2 -5
  52. package/index.js +3 -65
  53. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +67 -13
  54. package/infrastructure/scheduler/index.js +6 -19
  55. package/infrastructure/scheduler/scheduler-service-factory.js +7 -55
  56. package/integrations/index.js +0 -1
  57. package/integrations/integration-base.js +29 -0
  58. package/integrations/integration-router.js +96 -131
  59. package/integrations/repositories/integration-mapping-repository-documentdb.js +50 -0
  60. package/integrations/repositories/integration-mapping-repository-interface.js +15 -0
  61. package/integrations/repositories/integration-mapping-repository-mongo.js +29 -0
  62. package/integrations/repositories/integration-mapping-repository-postgres.js +30 -0
  63. package/integrations/repositories/integration-mapping-repository.js +29 -0
  64. package/integrations/repositories/integration-repository-documentdb.js +35 -35
  65. package/integrations/repositories/integration-repository-interface.js +31 -17
  66. package/integrations/repositories/integration-repository-mongo.js +129 -60
  67. package/integrations/repositories/integration-repository-postgres.js +134 -62
  68. package/integrations/repositories/process-repository-mongo.js +1 -1
  69. package/integrations/tests/doubles/test-integration-repository.js +1 -12
  70. package/integrations/use-cases/add-entities-to-integration.js +92 -0
  71. package/integrations/use-cases/create-integration.js +75 -72
  72. package/integrations/use-cases/get-integrations-for-user.js +40 -80
  73. package/integrations/use-cases/index.js +1 -7
  74. package/integrations/use-cases/remove-entities-from-integration.js +70 -0
  75. package/integrations/utils/assert-entity-ids-input.js +42 -0
  76. package/integrations/utils/map-integration-dto.js +14 -43
  77. package/jest-setup.js +13 -2
  78. package/jest-teardown.js +5 -2
  79. package/jest.config.js +4 -0
  80. package/jest.db.config.js +23 -0
  81. package/modules/index.js +0 -14
  82. package/modules/module-factory.js +0 -12
  83. package/modules/module.js +2 -53
  84. package/modules/repositories/module-repository-documentdb.js +6 -51
  85. package/modules/repositories/module-repository-interface.js +2 -19
  86. package/modules/repositories/module-repository-mongo.js +74 -67
  87. package/modules/repositories/module-repository-postgres.js +74 -66
  88. package/modules/repositories/module-repository.js +1 -2
  89. package/modules/requester/requester.js +2 -27
  90. package/modules/use-cases/get-entities-for-user.js +13 -26
  91. package/modules/use-cases/get-entity-options-by-id.js +4 -10
  92. package/modules/use-cases/get-entity-options-by-type.js +1 -1
  93. package/modules/use-cases/get-module-instance-from-type.js +1 -1
  94. package/modules/use-cases/get-module.js +4 -10
  95. package/modules/use-cases/process-authorization-callback.js +19 -53
  96. package/modules/use-cases/refresh-entity-options.js +4 -10
  97. package/modules/use-cases/test-module-auth.js +4 -10
  98. package/modules/utils/map-module-dto.js +5 -3
  99. package/modules/utils/to-credential-id.js +14 -0
  100. package/package.json +7 -6
  101. package/prisma-mongodb/schema.prisma +0 -92
  102. package/prisma-postgresql/schema.prisma +0 -87
  103. package/queues/index.js +0 -25
  104. package/queues/queuer-util.js +6 -5
  105. package/types/index.d.ts +0 -2
  106. package/user/repositories/user-repository-documentdb.js +2 -27
  107. package/user/repositories/user-repository-interface.js +0 -18
  108. package/user/repositories/user-repository-mongo.js +0 -24
  109. package/user/repositories/user-repository-postgres.js +0 -18
  110. package/user/tests/doubles/test-user-repository.js +1 -1
  111. package/user/use-cases/authenticate-user.js +1 -7
  112. package/user/use-cases/get-user-from-adopter-jwt.js +124 -95
  113. package/user/use-cases/get-user-from-bearer-token.js +7 -2
  114. package/user/use-cases/get-user-from-x-frigg-headers.js +80 -83
  115. package/user/user.js +1 -64
  116. package/credential/use-cases/credential-operations-for-user.js +0 -202
  117. package/credential/use-cases/reauthorize-credential-for-user.js +0 -73
  118. package/database/encryption/encryption-config.js +0 -219
  119. package/database/encryption/test-support/fake-ciphertext.js +0 -15
  120. package/database/organization-where.js +0 -18
  121. package/database/unique-violation.js +0 -25
  122. package/extensions/bootstrap-runner.js +0 -43
  123. package/extensions/extension-loader.js +0 -187
  124. package/extensions/index.js +0 -41
  125. package/extensions/initialize-app.js +0 -123
  126. package/extensions/route-mounter.js +0 -103
  127. package/extensions/schema-composer.js +0 -188
  128. package/infrastructure/scheduler/netlify-scheduler-adapter.js +0 -220
  129. package/infrastructure/scheduler/scheduled-job-repository.js +0 -118
  130. package/integrations/repositories/integration-not-found.js +0 -14
  131. package/integrations/schedule-config.js +0 -80
  132. package/integrations/use-cases/dispatch-ongoing-syncs.js +0 -100
  133. package/integrations/use-cases/integration-operations-for-user.js +0 -256
  134. package/integrations/use-cases/resolve-integration-ownership.js +0 -177
  135. package/management-api/create-management-api-router.js +0 -49
  136. package/management-api/error-response.js +0 -116
  137. package/management-api/management-api-config.js +0 -151
  138. package/management-api/meta-router.js +0 -76
  139. package/management-api/openapi/build-openapi.js +0 -144
  140. package/management-api/openapi/components.js +0 -408
  141. package/management-api/openapi/v1-operations.js +0 -83
  142. package/management-api/openapi/v2-operations.js +0 -264
  143. package/management-api/route-registry.js +0 -411
  144. package/management-api/test-support/http-client.js +0 -39
  145. package/management-api/use-cases/get-management-api-meta.js +0 -57
  146. package/management-api/v1-deprecation.js +0 -120
  147. package/management-api/v2/create-v2-router.js +0 -85
  148. package/management-api/v2/dependencies.js +0 -281
  149. package/management-api/v2/dto.js +0 -143
  150. package/management-api/v2/handlers/authorization.js +0 -94
  151. package/management-api/v2/handlers/credentials.js +0 -33
  152. package/management-api/v2/handlers/entities.js +0 -77
  153. package/management-api/v2/handlers/integrations.js +0 -128
  154. package/management-api/v2/handlers/proxy.js +0 -34
  155. package/management-api/v2/request.js +0 -85
  156. package/management-api/v2/test-support/fake-dependencies.js +0 -103
  157. package/modules/domain/authorization-session.js +0 -144
  158. package/modules/entity-scope.js +0 -15
  159. package/modules/entity-type-registry.js +0 -311
  160. package/modules/proxy/proxy-errors.js +0 -20
  161. package/modules/proxy/proxy-path.js +0 -179
  162. package/modules/proxy/proxy-policy.js +0 -248
  163. package/modules/repositories/authorization-session-repository-documentdb.js +0 -112
  164. package/modules/repositories/authorization-session-repository-factory.js +0 -31
  165. package/modules/repositories/authorization-session-repository-interface.js +0 -47
  166. package/modules/repositories/authorization-session-repository-prisma.js +0 -87
  167. package/modules/repositories/entity-not-found.js +0 -18
  168. package/modules/requester/proxy-request.js +0 -244
  169. package/modules/scope-config.js +0 -192
  170. package/modules/scope-policy.js +0 -112
  171. package/modules/scope-resolution.js +0 -288
  172. package/modules/scope-visibility.js +0 -247
  173. package/modules/tests/doubles/in-memory-authorization-session-repository.js +0 -45
  174. package/modules/tests/doubles/ownership-matching.js +0 -18
  175. package/modules/use-cases/authorize-entity-for-user.js +0 -61
  176. package/modules/use-cases/create-entity-from-credential.js +0 -113
  177. package/modules/use-cases/entity-operations-for-user.js +0 -162
  178. package/modules/use-cases/entity-types.js +0 -139
  179. package/modules/use-cases/execute-entity-proxy-request.js +0 -286
  180. package/modules/use-cases/get-authorization-requirements.js +0 -100
  181. package/modules/use-cases/get-entity-module-for-user.js +0 -113
  182. package/modules/use-cases/manage-access.js +0 -21
  183. package/modules/use-cases/multi-step-authorization.js +0 -329
  184. package/prisma-postgresql/migrations/20261003000000_add_authorization_session/migration.sql +0 -28
  185. package/prisma-postgresql/migrations/20261015000000_add_organization_membership/migration.sql +0 -26
  186. package/prisma-postgresql/migrations/20261016000000_add_entity_integration_organization/migration.sql +0 -25
  187. package/prisma-postgresql/migrations/20261017000000_add_scheduled_job/migration.sql +0 -28
  188. package/providers/index.js +0 -23
  189. package/providers/resolve-provider.js +0 -93
  190. package/providers/runtime-detection.js +0 -124
  191. package/queues/providers/index.js +0 -11
  192. package/queues/providers/netlify-background-provider.js +0 -187
  193. package/queues/providers/qstash-queue-provider.js +0 -219
  194. package/queues/providers/sqs-queue-provider.js +0 -70
  195. package/queues/queue-client-interface.js +0 -55
  196. package/queues/queue-message-auth.js +0 -193
  197. package/queues/queue-provider-factory.js +0 -101
  198. package/queues/queue-provider.js +0 -65
  199. package/queues/sqs-queue-client.js +0 -81
  200. package/types/modules/index.d.ts +0 -197
  201. package/types/user/index.d.ts +0 -134
  202. package/user/adopter-jwt-config.js +0 -177
  203. package/user/adopter-jwt-errors.js +0 -59
  204. package/user/adopter-token-verifier.js +0 -122
  205. package/user/app-user-helpers.js +0 -97
  206. package/user/create-user-authentication.js +0 -64
  207. package/user/membership.js +0 -51
  208. package/user/repositories/organization-membership-record.js +0 -19
  209. package/user/repositories/organization-membership-repository-documentdb.js +0 -106
  210. package/user/repositories/organization-membership-repository-factory.js +0 -42
  211. package/user/repositories/organization-membership-repository-interface.js +0 -57
  212. package/user/repositories/organization-membership-repository-mongo.js +0 -13
  213. package/user/repositories/organization-membership-repository-postgres.js +0 -29
  214. package/user/repositories/organization-membership-repository-prisma.js +0 -81
  215. package/user/tests/doubles/adopter-jwt-fixtures.js +0 -220
  216. package/user/use-cases/ensure-organization-membership.js +0 -92
  217. package/user/use-cases/find-individual-users-by-organization.js +0 -54
@@ -2,17 +2,9 @@
2
2
  const {
3
3
  mapIntegrationClassToIntegrationDTO,
4
4
  } = require('../utils/map-integration-dto');
5
- const {
6
- describeRecord,
7
- visibilityClauses,
8
- matchesListFilters,
9
- } = require('../../modules/scope-visibility');
10
- const { DEFAULT_SCOPE_POLICY } = require('../../modules/scope-policy');
11
5
 
12
6
  /**
13
- * Use case for retrieving the integrations a caller may see: the ones they own
14
- * or their active organization owns, created in that organization (ADR-056
15
- * §3.6), each with `tenancy` ({scope, owner, organizationId, access}).
7
+ * Use case for retrieving all integrations for a specific user.
16
8
  * @class GetIntegrationsForUser
17
9
  */
18
10
  class GetIntegrationsForUser {
@@ -23,16 +15,13 @@ class GetIntegrationsForUser {
23
15
  * @param {Array<import('../integration').Integration>} params.integrationClasses - Array of available integration classes.
24
16
  * @param {import('../../modules/module-factory').ModuleFactory} params.moduleFactory - Service for module instantiation and management.
25
17
  * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository - Repository for module and entity data operations.
26
- * @param {Object} [params.scopePolicy] - from createScopePolicy
27
18
  */
28
19
  constructor({
29
20
  integrationRepository,
30
21
  integrationClasses,
31
22
  moduleFactory,
32
23
  moduleRepository,
33
- scopePolicy,
34
24
  }) {
35
- this.scopePolicy = scopePolicy || DEFAULT_SCOPE_POLICY;
36
25
  /**
37
26
  * @type {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface}
38
27
  */
@@ -43,86 +32,57 @@ class GetIntegrationsForUser {
43
32
  }
44
33
 
45
34
  /**
35
+ * Executes the retrieval of all integrations for a user.
46
36
  * @async
47
- * @param {Object} user - authenticated User
48
- * @param {{scope?: string, owner?: string}} [filters] - `?scope=` and `?owner=`
49
- * @returns {Promise<Object[]>} Array of integration DTOs.
37
+ * @param {string} userId - ID of the user whose integrations to retrieve.
38
+ * @returns {Promise<Object[]>} Array of integration DTOs for the specified user.
50
39
  */
51
- async execute(user, filters = {}) {
52
- const ctx = this.scopePolicy.accessContext(user);
53
- const records = await this.integrationRepository.findIntegrationsByOwnership(
54
- visibilityClauses(ctx, 'integration')
55
- );
40
+ async execute(userId) {
41
+ const integrationRecords =
42
+ await this.integrationRepository.findIntegrationsByUserId(userId);
56
43
 
57
44
  const integrations = [];
58
- for (const record of records) {
59
- // The query is the filter; the same rule again means a repository
60
- // that returns too much cannot widen the list.
61
- const tenancy = describeRecord(record, ctx, 'integration');
62
- if (!tenancy || !matchesListFilters(tenancy, filters)) continue;
63
- integrations.push({ ...(await this.toDto(record)), tenancy });
64
- }
65
- return integrations;
66
- }
67
45
 
68
- /** Hydrates one stored integration record into its DTO. */
69
- async toDto(integrationRecord) {
70
- const entities = await this.moduleRepository.findEntitiesByIds(
71
- integrationRecord.entitiesIds
72
- );
73
-
74
- const integrationClass = this.integrationClasses.find(
75
- (integrationClass) =>
76
- integrationClass.Definition.name ===
77
- integrationRecord.config.type
78
- );
46
+ for (const integrationRecord of integrationRecords) {
47
+ const entities = await this.moduleRepository.findEntitiesByIds(
48
+ integrationRecord.entitiesIds
49
+ );
79
50
 
80
- const modules = [];
81
- for (const entity of entities) {
82
- const moduleInstance = await this.moduleFactory.getModuleInstance(
83
- entity.id,
84
- integrationRecord.userId
51
+ const integrationClass = this.integrationClasses.find(
52
+ (integrationClass) =>
53
+ integrationClass.Definition.name ===
54
+ integrationRecord.config.type
85
55
  );
86
- modules.push(moduleInstance);
87
- }
88
56
 
89
- return mapIntegrationClassToIntegrationDTO({
90
- id: integrationRecord.id,
91
- userId: integrationRecord.userId,
92
- entities: entities,
93
- config: integrationRecord.config,
94
- status: integrationRecord.status,
95
- version: integrationRecord.version,
96
- messages: integrationRecord.messages || { errors: [], warnings: [] },
97
- modules,
98
- options: integrationClass.getOptionDetails(),
99
- });
100
- }
101
- }
57
+ const modules = [];
58
+ for (const entity of entities) {
59
+ const moduleInstance =
60
+ await this.moduleFactory.getModuleInstance(
61
+ entity.id,
62
+ integrationRecord.userId
63
+ );
64
+ modules.push(moduleInstance);
65
+ }
102
66
 
103
- /**
104
- * Every integration one user id owns, with no organization rules: the lookup
105
- * behind the app-level `friggCommands.findIntegrationsByUserId`, which has a
106
- * user id and no request.
107
- */
108
- class GetIntegrationsByUserId {
109
- /** @param {ConstructorParameters<typeof GetIntegrationsForUser>[0]} params */
110
- constructor(params) {
111
- this.integrationRepository = params.integrationRepository;
112
- this.hydrator = new GetIntegrationsForUser(params);
113
- }
67
+ const integrationData = {
68
+ id: integrationRecord.id,
69
+ userId: integrationRecord.userId,
70
+ entities: entities,
71
+ config: integrationRecord.config,
72
+ status: integrationRecord.status,
73
+ version: integrationRecord.version,
74
+ messages: integrationRecord.messages || { errors: [], warnings: [] },
75
+ modules,
76
+ options: integrationClass.getOptionDetails(),
77
+ };
114
78
 
115
- /** @param {string} userId */
116
- async execute(userId) {
117
- const records = await this.integrationRepository.findIntegrationsByUserId(
118
- userId
119
- );
120
- const integrations = [];
121
- for (const record of records) {
122
- integrations.push(await this.hydrator.toDto(record));
79
+ integrations.push(
80
+ mapIntegrationClassToIntegrationDTO(integrationData)
81
+ );
123
82
  }
83
+
124
84
  return integrations;
125
85
  }
126
86
  }
127
87
 
128
- module.exports = { GetIntegrationsForUser, GetIntegrationsByUserId };
88
+ module.exports = { GetIntegrationsForUser };
@@ -1,7 +1,4 @@
1
- const {
2
- GetIntegrationsForUser,
3
- GetIntegrationsByUserId,
4
- } = require('./get-integrations-for-user');
1
+ const { GetIntegrationsForUser } = require('./get-integrations-for-user');
5
2
  const { DeleteIntegrationForUser } = require('./delete-integration-for-user');
6
3
  const { CreateIntegration } = require('./create-integration');
7
4
  const { GetIntegration } = require('./get-integration');
@@ -9,11 +6,9 @@ const { CreateProcess } = require('./create-process');
9
6
  const { UpdateProcessState } = require('./update-process-state');
10
7
  const { UpdateProcessMetrics } = require('./update-process-metrics');
11
8
  const { GetProcess } = require('./get-process');
12
- const { DispatchOngoingSyncs } = require('./dispatch-ongoing-syncs');
13
9
 
14
10
  module.exports = {
15
11
  GetIntegrationsForUser,
16
- GetIntegrationsByUserId,
17
12
  DeleteIntegrationForUser,
18
13
  CreateIntegration,
19
14
  GetIntegration,
@@ -21,5 +16,4 @@ module.exports = {
21
16
  UpdateProcessState,
22
17
  UpdateProcessMetrics,
23
18
  GetProcess,
24
- DispatchOngoingSyncs,
25
19
  };
@@ -0,0 +1,70 @@
1
+ const {
2
+ assertEntityIdsInput,
3
+ integrationNotFound,
4
+ loadIntegration,
5
+ } = require('../utils/assert-entity-ids-input');
6
+
7
+ /**
8
+ * Detaches entities from an integration. Entities that are not attached are
9
+ * ignored, and detached entities are not deleted. Every module the
10
+ * integration holds must keep at least one entity, so the integration can
11
+ * still hydrate each of its modules.
12
+ * @class RemoveEntitiesFromIntegration
13
+ */
14
+ class RemoveEntitiesFromIntegration {
15
+ /**
16
+ * @param {Object} params
17
+ * @param {import('../repositories/integration-repository-interface').IntegrationRepositoryInterface} params.integrationRepository
18
+ * @param {import('../../modules/repositories/module-repository-interface').ModuleRepositoryInterface} params.moduleRepository
19
+ */
20
+ constructor({ integrationRepository, moduleRepository }) {
21
+ this.integrationRepository = integrationRepository;
22
+ this.moduleRepository = moduleRepository;
23
+ }
24
+
25
+ /**
26
+ * @param {string} integrationId
27
+ * @param {string[]} entityIds
28
+ * @returns {Promise<Object>} The updated integration record.
29
+ */
30
+ async execute(integrationId, entityIds) {
31
+ assertEntityIdsInput(integrationId, entityIds);
32
+
33
+ const integration = await loadIntegration(
34
+ this.integrationRepository,
35
+ integrationId
36
+ );
37
+
38
+ const removing = new Set(entityIds.map(String));
39
+ const attached = await this.moduleRepository.findEntitiesByIds(
40
+ integration.entitiesIds ?? []
41
+ );
42
+ const emptied = [...new Set(attached.map((e) => e.moduleName))].filter(
43
+ (moduleName) =>
44
+ !attached.some(
45
+ (e) =>
46
+ e.moduleName === moduleName &&
47
+ !removing.has(String(e.id))
48
+ )
49
+ );
50
+ if (emptied.length > 0) {
51
+ const error = new Error(
52
+ `Removing these entities would leave no entity for module(s): ${emptied.join(
53
+ ', '
54
+ )}`
55
+ );
56
+ error.code = 'LAST_ENTITY_OF_MODULE';
57
+ throw error;
58
+ }
59
+
60
+ const updated =
61
+ await this.integrationRepository.removeEntitiesFromIntegration(
62
+ integrationId,
63
+ entityIds
64
+ );
65
+ if (!updated) throw integrationNotFound(integrationId);
66
+ return updated;
67
+ }
68
+ }
69
+
70
+ module.exports = { RemoveEntitiesFromIntegration };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Throws INVALID_INTEGRATION_DATA unless an integration id and a non-empty
3
+ * list of entity ids are given.
4
+ * @param {string} integrationId
5
+ * @param {string[]} entityIds
6
+ */
7
+ function assertEntityIdsInput(integrationId, entityIds) {
8
+ if (!integrationId || !Array.isArray(entityIds) || entityIds.length === 0) {
9
+ const error = new Error(
10
+ 'integrationId and a non-empty entityIds array are required'
11
+ );
12
+ error.code = 'INVALID_INTEGRATION_DATA';
13
+ throw error;
14
+ }
15
+ }
16
+
17
+ function integrationNotFound(integrationId) {
18
+ const error = new Error(`Integration ${integrationId} not found`);
19
+ error.code = 'INTEGRATION_NOT_FOUND';
20
+ return error;
21
+ }
22
+
23
+ /**
24
+ * Loads an integration, mapping the repositories' "not found" error to
25
+ * INTEGRATION_NOT_FOUND. Any other failure (connection, invalid id) is
26
+ * rethrown unchanged.
27
+ * @param {Object} integrationRepository
28
+ * @param {string} integrationId
29
+ * @returns {Promise<Object>} The integration record.
30
+ */
31
+ async function loadIntegration(integrationRepository, integrationId) {
32
+ try {
33
+ return await integrationRepository.findIntegrationById(integrationId);
34
+ } catch (error) {
35
+ if (/^Integration with id .* not found$/.test(error?.message)) {
36
+ throw integrationNotFound(integrationId);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ module.exports = { assertEntityIdsInput, integrationNotFound, loadIntegration };
@@ -19,48 +19,19 @@ function mapIntegrationClassToIntegrationDTO(integration) {
19
19
  };
20
20
  }
21
21
 
22
- let deprecationLogged = false;
23
22
 
24
- /**
25
- * @deprecated Since ADR-059. Read `entityTypeRegistry` / `moduleDefinitions`
26
- * from `loadAppDefinition()`, or call `getEntityTypeRegistry(appDefinition)`
27
- * and read each entry's `definition`.
28
- *
29
- * Kept as a wrapper over the entity type registry so callers that imported it
30
- * keep working. It returns the registry's definitions, so it follows the
31
- * registry's rules: one definition per entity type name (the first wins when
32
- * two integrations bind different definitions under one name, as
33
- * ModuleFactory already picked), and, when `entities` is passed, registry-only
34
- * types are included and conflicts throw `EntityRegistryError`.
35
- *
36
- * @param {Array<Function>} integrationClasses - integration classes
37
- * @param {Object} [entities] - `appDefinition.entities` (ADR-059)
38
- * @returns {Array<Object>} API module definitions
39
- */
40
- function getModulesDefinitionFromIntegrationClasses(
41
- integrationClasses,
42
- entities
43
- ) {
44
- // Required here, not at the top: modules/ must not load at require time
45
- // of every integration use case that only wants the DTO mapper.
46
- const {
47
- getEntityTypeRegistry,
48
- getEntityTypeDefinitions,
49
- } = require('../../modules/entity-type-registry');
50
- if (!deprecationLogged) {
51
- deprecationLogged = true;
52
- const { getLogger } = require('../../logs');
53
- getLogger('frigg.integrations').warn(
54
- 'getModulesDefinitionFromIntegrationClasses is deprecated; use getEntityTypeRegistry (ADR-059)',
55
- { eventName: 'frigg.integrations.deprecated_module_definitions' }
56
- );
57
- }
58
- return getEntityTypeDefinitions(
59
- getEntityTypeRegistry({ integrations: integrationClasses, entities })
60
- );
61
- }
62
-
63
- module.exports = {
64
- mapIntegrationClassToIntegrationDTO,
65
- getModulesDefinitionFromIntegrationClasses,
23
+ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
24
+ return [
25
+ ...new Set(
26
+ integrationClasses
27
+ .map((integration) =>
28
+ Object.values(integration.Definition.modules).map(
29
+ (module) => module.definition
30
+ )
31
+ )
32
+ .flat()
33
+ ),
34
+ ];
66
35
  };
36
+
37
+ module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
package/jest-setup.js CHANGED
@@ -1,2 +1,13 @@
1
- const {globalSetup} = require('@friggframework/test');
2
- module.exports = globalSetup;
1
+ // Core's unit suites must not need a database, so this global setup only
2
+ // sets the test environment. It deliberately does not start an in-memory
3
+ // MongoDB (as @friggframework/test's globalSetup does): nothing in the default
4
+ // suite talks to a live server, and a binary that failed to start used to stop
5
+ // every core suite from running. Suites that need a real database are
6
+ // `*.db.test.js` files, run by `npm run test:db` (see jest.db.config.js).
7
+ const { overrideEnvironment } = require('@friggframework/test');
8
+
9
+ module.exports = async function () {
10
+ if (!process.env.STAGE) {
11
+ overrideEnvironment({ STAGE: 'dev' });
12
+ }
13
+ };
package/jest-teardown.js CHANGED
@@ -1,2 +1,5 @@
1
- const {globalTeardown} = require('@friggframework/test');
2
- module.exports = globalTeardown;
1
+ const { restoreEnvironment } = require('@friggframework/test');
2
+
3
+ module.exports = async function () {
4
+ restoreEnvironment();
5
+ };
package/jest.config.js CHANGED
@@ -20,4 +20,8 @@ module.exports = {
20
20
  globalTeardown: './jest-teardown.js',
21
21
 
22
22
  setupFilesAfterEnv: ['./logs/jest-logger-setup.js'],
23
+
24
+ // `*.db.test.js` suites talk to a real MongoDB replica set / PostgreSQL
25
+ // and run separately via `npm run test:db` (jest.db.config.js).
26
+ testPathIgnorePatterns: ['/node_modules/', '\\.db\\.test\\.js$'],
23
27
  };
@@ -0,0 +1,23 @@
1
+ /*
2
+ * Database integration suites (`*.db.test.js`).
3
+ *
4
+ * These talk to real databases instead of mocking Prisma, so they are kept
5
+ * out of the default `npm test` run and executed with `npm run test:db`.
6
+ * They need generated Prisma clients (`npm run prisma:generate`) and:
7
+ * - a MongoDB replica set (Prisma requires one for MongoDB writes):
8
+ * FRIGG_TEST_MONGODB_URL, default
9
+ * mongodb://localhost:27017/frigg?replicaSet=rs0
10
+ * - PostgreSQL with the prisma-postgresql migrations applied:
11
+ * FRIGG_TEST_POSTGRES_URL, default
12
+ * postgresql://postgres:postgres@localhost:5432/frigg?schema=public
13
+ * The repository parity suites skip themselves unless their own URLs are
14
+ * set (INTEGRATION_ENTITIES_PARITY_* / QUERY_MAPPINGS_PARITY_*).
15
+ */
16
+ const base = require('./jest.config');
17
+
18
+ module.exports = {
19
+ ...base,
20
+ coverageThreshold: undefined,
21
+ testMatch: ['**/*.db.test.js'],
22
+ testPathIgnorePatterns: ['/node_modules/'],
23
+ };
package/modules/index.js CHANGED
@@ -4,15 +4,6 @@ const { OAuth2Requester } = require('./requester/oauth-2');
4
4
  const { Requester } = require('./requester/requester');
5
5
  const { ModuleConstants } = require('./ModuleConstants');
6
6
  const { ModuleFactory } = require('./module-factory');
7
- const {
8
- getEntityTypeRegistry,
9
- EntityRegistryError,
10
- } = require('./entity-type-registry');
11
- const {
12
- resolveScope,
13
- listBindingScopes,
14
- OwnershipConfigError,
15
- } = require('./entity-scope');
16
7
 
17
8
  module.exports = {
18
9
  ApiKeyRequester,
@@ -21,9 +12,4 @@ module.exports = {
21
12
  Requester,
22
13
  ModuleConstants,
23
14
  ModuleFactory,
24
- getEntityTypeRegistry,
25
- EntityRegistryError,
26
- resolveScope,
27
- listBindingScopes,
28
- OwnershipConfigError,
29
15
  };
@@ -45,18 +45,6 @@ class ModuleFactory {
45
45
  );
46
46
  }
47
47
 
48
- // definition.env may be an async function (e.g. the db-credentials
49
- // extension's bootstrap loads OAuth app credentials from the
50
- // database); resolve it first. A static env keeps the plain
51
- // constructor, exactly as before.
52
- if (typeof moduleDefinition.env === 'function') {
53
- return await Module.create({
54
- userId,
55
- entity,
56
- definition: moduleDefinition,
57
- });
58
- }
59
-
60
48
  return new Module({
61
49
  userId,
62
50
  entity,
package/modules/module.js CHANGED
@@ -21,17 +21,8 @@ class Module extends Delegate {
21
21
  * @param {string} params.userId The user id
22
22
  * @param {Object} params.entity The entity record from the database
23
23
  * @param {string} [params.state] Optional OAuth state value forwarded to the API client (round-trips through the OAuth provider).
24
- * @param {Object} [params.resolvedEnv] definition.env as Module.create() resolved it (may be undefined).
25
- * @param {boolean} [params.envResolved=false] true when resolvedEnv carries the resolved env, even an undefined one; otherwise the static definition.env is used.
26
24
  */
27
- constructor({
28
- definition,
29
- userId = null,
30
- entity: entityObj = null,
31
- state = null,
32
- resolvedEnv,
33
- envResolved = false,
34
- }) {
25
+ constructor({ definition, userId = null, entity: entityObj = null, state = null }) {
35
26
  super({ definition, userId, entity: entityObj });
36
27
 
37
28
  this.validateDefinition(definition);
@@ -63,23 +54,8 @@ class Module extends Delegate {
63
54
 
64
55
  Object.assign(this, this.definition.requiredAuthMethods);
65
56
 
66
- // A pre-resolved env (Module.create()) supports definition.env as an
67
- // async function, e.g. OAuth app credentials loaded from the database
68
- // by the db-credentials extension; otherwise the static object.
69
- // "Resolved" is an explicit flag, not a non-null value: a function env
70
- // may legitimately resolve to undefined (no static env, no DB row),
71
- // and falling back to definition.env would then read the function.
72
- const envValues = envResolved ? resolvedEnv : this.definition.env;
73
- if (typeof envValues === 'function') {
74
- // Spreading a function yields no keys, so the API client would
75
- // silently get no client_id / client_secret.
76
- throw new TypeError(
77
- `Module ${this.name}: definition.env is a function; build the module with await Module.create(...)`
78
- );
79
- }
80
-
81
57
  const apiParams = {
82
- ...envValues,
58
+ ...this.definition.env,
83
59
  delegate: this,
84
60
  logger: this.logger,
85
61
  ...(state ? { state } : {}),
@@ -91,33 +67,6 @@ class Module extends Delegate {
91
67
  this.api = new this.apiClass(apiParams);
92
68
  }
93
69
 
94
- /**
95
- * Resolves definition.env, supporting both a static object and a
96
- * (possibly async) function.
97
- *
98
- * @param {Object} definition - Module definition
99
- * @returns {Promise<Object|undefined>} Resolved env values
100
- */
101
- static async resolveEnv(definition) {
102
- if (typeof definition.env === 'function') {
103
- return await definition.env();
104
- }
105
- return definition.env;
106
- }
107
-
108
- /**
109
- * Async factory that resolves definition.env first, so a module whose
110
- * env is a function gets its values. Prefer it over `new Module(...)`
111
- * wherever the call site can await.
112
- *
113
- * @param {Object} params - same as the constructor, minus resolvedEnv
114
- * @returns {Promise<Module>}
115
- */
116
- static async create({ definition, userId = null, entity = null, state = null }) {
117
- const resolvedEnv = await Module.resolveEnv(definition);
118
- return new Module({ definition, userId, entity, state, resolvedEnv, envResolved: true });
119
- }
120
-
121
70
  getName() {
122
71
  return this.name;
123
72
  }