@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -2,12 +2,12 @@ const Boom = require('@hapi/boom');
2
2
 
3
3
  /**
4
4
  * Use case for authenticating a user using multiple authentication strategies.
5
- *
5
+ *
6
6
  * Supports three authentication modes in priority order:
7
7
  * 1. Shared Secret (backend-to-backend with x-frigg-api-key + x-frigg headers)
8
8
  * 2. Adopter JWT (custom JWT authentication)
9
9
  * 3. Frigg Native Token (bearer token from /user/login)
10
- *
10
+ *
11
11
  * x-frigg-appUserId and x-frigg-appOrgId headers are automatically supported
12
12
  * for user identification with any auth mode. When present with JWT or Frigg
13
13
  * tokens, they are validated to match the authenticated user.
@@ -123,5 +123,3 @@ class AuthenticateUser {
123
123
  }
124
124
 
125
125
  module.exports = { AuthenticateUser };
126
-
127
-
@@ -33,7 +33,7 @@ class AuthenticateWithSharedSecret {
33
33
  if (!expectedSecret) {
34
34
  throw Boom.badImplementation(
35
35
  'FRIGG_API_KEY environment variable is not configured. ' +
36
- 'Set FRIGG_API_KEY to enable shared secret authentication.'
36
+ 'Set FRIGG_API_KEY to enable shared secret authentication.'
37
37
  );
38
38
  }
39
39
 
@@ -39,13 +39,14 @@ class CreateIndividualUser {
39
39
  const appUserId = get(params, 'appUserId', null);
40
40
  const organizationUserId = get(params, 'organizationUserId', null);
41
41
 
42
- const individualUserData = await this.userRepository.createIndividualUser({
43
- email,
44
- username,
45
- hashword,
46
- appUserId,
47
- organizationUser: organizationUserId,
48
- });
42
+ const individualUserData =
43
+ await this.userRepository.createIndividualUser({
44
+ email,
45
+ username,
46
+ hashword,
47
+ appUserId,
48
+ organizationUser: organizationUserId,
49
+ });
49
50
 
50
51
  return new User(
51
52
  individualUserData,
@@ -44,4 +44,4 @@ class CreateOrganizationUser {
44
44
  }
45
45
  }
46
46
 
47
- module.exports = { CreateOrganizationUser };
47
+ module.exports = { CreateOrganizationUser };
@@ -27,4 +27,4 @@ class CreateTokenForUserId {
27
27
  }
28
28
  }
29
29
 
30
- module.exports = { CreateTokenForUserId };
30
+ module.exports = { CreateTokenForUserId };
@@ -0,0 +1,47 @@
1
+ const { get } = require('../../assertions');
2
+ const Boom = require('@hapi/boom');
3
+
4
+ /**
5
+ * Use case for deleting a user.
6
+ * @class DeleteUser
7
+ */
8
+ class DeleteUser {
9
+ /**
10
+ * Creates a new DeleteUser instance.
11
+ * @param {Object} params - Configuration parameters.
12
+ * @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
13
+ */
14
+ constructor({ userRepository }) {
15
+ this.userRepository = userRepository;
16
+ }
17
+
18
+ /**
19
+ * Executes the use case.
20
+ * @async
21
+ * @param {string} userId - The ID of the user to delete.
22
+ * @returns {Promise<boolean>} True if user was deleted successfully.
23
+ * @throws {Boom} If userId is not provided or user not found.
24
+ */
25
+ async execute(userId) {
26
+ if (!userId) {
27
+ throw Boom.badRequest('userId is required');
28
+ }
29
+
30
+ // First check if user exists
31
+ const user = await this.userRepository.findUserById(userId);
32
+ if (!user) {
33
+ throw Boom.notFound(`User with id ${userId} not found`);
34
+ }
35
+
36
+ // Delete the user
37
+ const deleted = await this.userRepository.deleteUser(userId);
38
+
39
+ if (!deleted) {
40
+ throw Boom.internal(`Failed to delete user with id ${userId}`);
41
+ }
42
+
43
+ return true;
44
+ }
45
+ }
46
+
47
+ module.exports = { DeleteUser };
@@ -145,5 +145,3 @@ class GetUserFromAdopterJwt {
145
145
  }
146
146
 
147
147
  module.exports = { GetUserFromAdopterJwt };
148
-
149
-
@@ -41,7 +41,10 @@ class GetUserFromBearerToken {
41
41
  }
42
42
 
43
43
  if (this.userConfig.primary === 'organization') {
44
- const organizationUserData = await this.userRepository.findOrganizationUserById(sessionToken.user);
44
+ const organizationUserData =
45
+ await this.userRepository.findOrganizationUserById(
46
+ sessionToken.user
47
+ );
45
48
 
46
49
  if (!organizationUserData) {
47
50
  throw Boom.unauthorized('Organization User Not Found');
@@ -57,7 +60,8 @@ class GetUserFromBearerToken {
57
60
  );
58
61
  }
59
62
 
60
- const individualUserData = await this.userRepository.findIndividualUserById(sessionToken.user);
63
+ const individualUserData =
64
+ await this.userRepository.findIndividualUserById(sessionToken.user);
61
65
 
62
66
  if (!individualUserData) {
63
67
  throw Boom.unauthorized('Individual User Not Found');
@@ -74,4 +78,4 @@ class GetUserFromBearerToken {
74
78
  }
75
79
  }
76
80
 
77
- module.exports = { GetUserFromBearerToken };
81
+ module.exports = { GetUserFromBearerToken };
@@ -76,10 +76,11 @@ class GetUserFromXFriggHeaders {
76
76
  }
77
77
 
78
78
  // Auto-link the users
79
- individualUserData = await this.userRepository.linkIndividualToOrganization(
80
- individualUserData.id,
81
- organizationUserData.id
82
- );
79
+ individualUserData =
80
+ await this.userRepository.linkIndividualToOrganization(
81
+ individualUserData.id,
82
+ organizationUserData.id
83
+ );
83
84
  }
84
85
  }
85
86
 
@@ -89,12 +90,13 @@ class GetUserFromXFriggHeaders {
89
90
  appUserId &&
90
91
  this.userConfig.individualUserRequired !== false
91
92
  ) {
92
- individualUserData =
93
- await this.userRepository.createIndividualUser({
93
+ individualUserData = await this.userRepository.createIndividualUser(
94
+ {
94
95
  appUserId,
95
96
  username: `app-user-${appUserId}`,
96
97
  email: `${appUserId}@app.local`,
97
- });
98
+ }
99
+ );
98
100
  }
99
101
 
100
102
  if (
@@ -109,10 +111,11 @@ class GetUserFromXFriggHeaders {
109
111
 
110
112
  // Link individual user to newly created org user if individual exists
111
113
  if (individualUserData && organizationUserData) {
112
- individualUserData = await this.userRepository.linkIndividualToOrganization(
113
- individualUserData.id,
114
- organizationUserData.id
115
- );
114
+ individualUserData =
115
+ await this.userRepository.linkIndividualToOrganization(
116
+ individualUserData.id,
117
+ organizationUserData.id
118
+ );
116
119
  }
117
120
  }
118
121
 
@@ -1,7 +1,5 @@
1
1
  const Boom = require('@hapi/boom');
2
- const {
3
- RequiredPropertyError,
4
- } = require('../../errors');
2
+ const { RequiredPropertyError } = require('../../errors');
5
3
  const { User } = require('../user');
6
4
 
7
5
  /**
@@ -93,11 +91,11 @@ class LoginUser {
93
91
  }
94
92
  }
95
93
 
96
-
97
94
  if (this.userConfig.organizationUserRequired) {
98
-
99
95
  const organizationUserData =
100
- await this.userRepository.findOrganizationUserByAppOrgId(appOrgId);
96
+ await this.userRepository.findOrganizationUserByAppOrgId(
97
+ appOrgId
98
+ );
101
99
 
102
100
  if (!organizationUserData) {
103
101
  throw Boom.unauthorized(`org user ${appOrgId} not found`);
@@ -115,8 +113,10 @@ class LoginUser {
115
113
  return organizationUser;
116
114
  }
117
115
 
118
- throw new Error('User configuration must require either individualUserRequired or organizationUserRequired');
116
+ throw new Error(
117
+ 'User configuration must require either individualUserRequired or organizationUserRequired'
118
+ );
119
119
  }
120
120
  }
121
121
 
122
- module.exports = { LoginUser };
122
+ module.exports = { LoginUser };
package/user/user.js CHANGED
@@ -14,7 +14,14 @@ class User {
14
14
  * @param {boolean} [individualUserRequired=true] - Whether the user is required to have an individual user.
15
15
  * @param {boolean} [organizationUserRequired=false] - Whether the user is required to have an organization user.
16
16
  */
17
- constructor(individualUser = null, organizationUser = null, usePassword = false, primary = 'individual', individualUserRequired = true, organizationUserRequired = false) {
17
+ constructor(
18
+ individualUser = null,
19
+ organizationUser = null,
20
+ usePassword = false,
21
+ primary = 'individual',
22
+ individualUserRequired = true,
23
+ organizationUserRequired = false
24
+ ) {
18
25
  this.individualUser = individualUser;
19
26
  this.organizationUser = organizationUser;
20
27
  this.usePassword = usePassword;
@@ -109,12 +116,19 @@ class User {
109
116
  }
110
117
 
111
118
  // When primary is 'organization', also check linked individual user
112
- if (this.config.primary === 'organization' && userIdStr === individualId) {
119
+ if (
120
+ this.config.primary === 'organization' &&
121
+ userIdStr === individualId
122
+ ) {
113
123
  return true;
114
124
  }
115
125
 
116
126
  // When primary is 'individual', also check linked organization user if required
117
- if (this.config.primary === 'individual' && this.config.organizationUserRequired && userIdStr === organizationId) {
127
+ if (
128
+ this.config.primary === 'individual' &&
129
+ this.config.organizationUserRequired &&
130
+ userIdStr === organizationId
131
+ ) {
118
132
  return true;
119
133
  }
120
134
 
@@ -122,4 +136,4 @@ class User {
122
136
  }
123
137
  }
124
138
 
125
- module.exports = { User };
139
+ module.exports = { User };
@@ -4,7 +4,7 @@ const PACKAGE_JSON = 'package.json';
4
4
 
5
5
  function findNearestBackendPackageJson() {
6
6
  let currentDir = process.cwd();
7
-
7
+
8
8
  // First check if we're in production by looking for package.json in the current directory
9
9
  const rootPackageJson = path.join(currentDir, PACKAGE_JSON);
10
10
  if (fs.existsSync(rootPackageJson)) {
@@ -35,4 +35,4 @@ function validateBackendPath(backendPath) {
35
35
  module.exports = {
36
36
  findNearestBackendPackageJson,
37
37
  validateBackendPath,
38
- };
38
+ };
package/utils/index.js CHANGED
@@ -1,4 +1,7 @@
1
- const { findNearestBackendPackageJson, validateBackendPath } = require('./backend-path');
1
+ const {
2
+ findNearestBackendPackageJson,
3
+ validateBackendPath,
4
+ } = require('./backend-path');
2
5
 
3
6
  module.exports = {
4
7
  findNearestBackendPackageJson,
@@ -1,8 +1,7 @@
1
1
  const { prisma } = require('../../database/prisma');
2
2
  const {
3
- ApiGatewayManagementApiClient,
4
- PostToConnectionCommand,
5
- } = require('@aws-sdk/client-apigatewaymanagementapi');
3
+ StaleConnectionError,
4
+ } = require('../websocket-message-sender-interface');
6
5
  const {
7
6
  toObjectId,
8
7
  fromObjectId,
@@ -16,10 +15,19 @@ const {
16
15
  WebsocketConnectionRepositoryInterface,
17
16
  } = require('./websocket-connection-repository-interface');
18
17
 
18
+ /**
19
+ * DocumentDB WebSocket Connection Repository Adapter
20
+ *
21
+ * BREAKING CHANGE (v3): A messageSender must be explicitly provided for
22
+ * WebSocket send functionality. For AWS API Gateway, pass
23
+ * `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
24
+ * See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
25
+ */
19
26
  class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionRepositoryInterface {
20
- constructor() {
27
+ constructor(messageSender = null) {
21
28
  super();
22
29
  this.prisma = prisma;
30
+ this._messageSender = messageSender;
23
31
  }
24
32
 
25
33
  async createConnection(connectionId) {
@@ -29,13 +37,21 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
29
37
  createdAt: now,
30
38
  updatedAt: now,
31
39
  };
32
- const insertedId = await insertOne(this.prisma, 'WebsocketConnection', document);
33
- const created = await findOne(this.prisma, 'WebsocketConnection', { _id: insertedId });
40
+ const insertedId = await insertOne(
41
+ this.prisma,
42
+ 'WebsocketConnection',
43
+ document
44
+ );
45
+ const created = await findOne(this.prisma, 'WebsocketConnection', {
46
+ _id: insertedId,
47
+ });
34
48
  return this._mapConnection(created);
35
49
  }
36
50
 
37
51
  async deleteConnection(connectionId) {
38
- const result = await deleteOne(this.prisma, 'WebsocketConnection', { connectionId });
52
+ const result = await deleteOne(this.prisma, 'WebsocketConnection', {
53
+ connectionId,
54
+ });
39
55
  const deleted = result?.n ?? 0;
40
56
  return { acknowledged: true, deletedCount: deleted };
41
57
  }
@@ -52,21 +68,28 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
52
68
  { projection: { connectionId: 1 } }
53
69
  );
54
70
 
71
+ if (!this._messageSender) {
72
+ throw new Error(
73
+ 'WebsocketConnectionRepositoryDocumentDB requires a messageSender for send functionality.\n' +
74
+ 'Pass one via constructor, e.g.:\n' +
75
+ ' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
76
+ ' new WebsocketConnectionRepositoryDocumentDB(new ApiGatewayMessageSender())\n' +
77
+ 'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
78
+ );
79
+ }
80
+ const sender = this._messageSender;
81
+
55
82
  return connections.map((conn) => ({
56
83
  connectionId: conn.connectionId,
57
84
  send: async (data) => {
58
- const apigwManagementApi = new ApiGatewayManagementApiClient({
59
- endpoint: process.env.WEBSOCKET_API_ENDPOINT,
60
- });
61
-
62
85
  try {
63
- const command = new PostToConnectionCommand({
64
- ConnectionId: conn.connectionId,
65
- Data: JSON.stringify(data),
66
- });
67
- await apigwManagementApi.send(command);
86
+ await sender.send(
87
+ conn.connectionId,
88
+ data,
89
+ process.env.WEBSOCKET_API_ENDPOINT
90
+ );
68
91
  } catch (error) {
69
- if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
92
+ if (error instanceof StaleConnectionError) {
70
93
  console.log(`Stale connection ${conn.connectionId}`);
71
94
  await deleteMany(this.prisma, 'WebsocketConnection', {
72
95
  connectionId: conn.connectionId,
@@ -80,14 +103,18 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
80
103
  }
81
104
 
82
105
  async findConnection(connectionId) {
83
- const doc = await findOne(this.prisma, 'WebsocketConnection', { connectionId });
106
+ const doc = await findOne(this.prisma, 'WebsocketConnection', {
107
+ connectionId,
108
+ });
84
109
  return doc ? this._mapConnection(doc) : null;
85
110
  }
86
111
 
87
112
  async findConnectionById(id) {
88
113
  const objectId = toObjectId(id);
89
114
  if (!objectId) return null;
90
- const doc = await findOne(this.prisma, 'WebsocketConnection', { _id: objectId });
115
+ const doc = await findOne(this.prisma, 'WebsocketConnection', {
116
+ _id: objectId,
117
+ });
91
118
  return doc ? this._mapConnection(doc) : null;
92
119
  }
93
120
 
@@ -115,5 +142,3 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
115
142
  }
116
143
 
117
144
  module.exports = { WebsocketConnectionRepositoryDocumentDB };
118
-
119
-
@@ -1,12 +1,6 @@
1
- const {
2
- WebsocketConnectionRepositoryMongo,
3
- } = require('./websocket-connection-repository-mongo');
4
1
  const {
5
2
  WebsocketConnectionRepositoryPostgres,
6
3
  } = require('./websocket-connection-repository-postgres');
7
- const {
8
- WebsocketConnectionRepositoryDocumentDB,
9
- } = require('./websocket-connection-repository-documentdb');
10
4
  const config = require('../../database/config');
11
5
 
12
6
  /**
@@ -20,12 +14,14 @@ function createWebsocketConnectionRepository() {
20
14
 
21
15
  switch (dbType) {
22
16
  case 'mongodb':
17
+ const { WebsocketConnectionRepositoryMongo } = require('./websocket-connection-repository-mongo');
23
18
  return new WebsocketConnectionRepositoryMongo();
24
19
 
25
20
  case 'postgresql':
26
21
  return new WebsocketConnectionRepositoryPostgres();
27
22
 
28
23
  case 'documentdb':
24
+ const { WebsocketConnectionRepositoryDocumentDB } = require('./websocket-connection-repository-documentdb');
29
25
  return new WebsocketConnectionRepositoryDocumentDB();
30
26
 
31
27
  default:
@@ -38,7 +34,7 @@ function createWebsocketConnectionRepository() {
38
34
  module.exports = {
39
35
  createWebsocketConnectionRepository,
40
36
  // Export adapters for direct testing
41
- WebsocketConnectionRepositoryMongo,
37
+ get WebsocketConnectionRepositoryMongo() { return require('./websocket-connection-repository-mongo').WebsocketConnectionRepositoryMongo; },
42
38
  WebsocketConnectionRepositoryPostgres,
43
- WebsocketConnectionRepositoryDocumentDB,
39
+ get WebsocketConnectionRepositoryDocumentDB() { return require('./websocket-connection-repository-documentdb').WebsocketConnectionRepositoryDocumentDB; },
44
40
  };
@@ -1,8 +1,7 @@
1
1
  const { prisma } = require('../../database/prisma');
2
2
  const {
3
- ApiGatewayManagementApiClient,
4
- PostToConnectionCommand,
5
- } = require('@aws-sdk/client-apigatewaymanagementapi');
3
+ StaleConnectionError,
4
+ } = require('../websocket-message-sender-interface');
6
5
  const {
7
6
  WebsocketConnectionRepositoryInterface,
8
7
  } = require('./websocket-connection-repository-interface');
@@ -14,12 +13,17 @@ const {
14
13
  * MongoDB-specific characteristics:
15
14
  * - Uses String IDs (ObjectId)
16
15
  * - No ID conversion needed (IDs are already strings)
17
- * - AWS API Gateway Management API integration preserved
16
+ *
17
+ * BREAKING CHANGE (v3): A messageSender must be explicitly provided for
18
+ * WebSocket send functionality. For AWS API Gateway, pass
19
+ * `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
20
+ * See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
18
21
  */
19
22
  class WebsocketConnectionRepositoryMongo extends WebsocketConnectionRepositoryInterface {
20
- constructor() {
23
+ constructor(messageSender = null) {
21
24
  super();
22
25
  this.prisma = prisma;
26
+ this._messageSender = messageSender;
23
27
  }
24
28
 
25
29
  /**
@@ -74,25 +78,31 @@ class WebsocketConnectionRepositoryMongo extends WebsocketConnectionRepositoryIn
74
78
  select: { connectionId: true },
75
79
  });
76
80
 
81
+ if (!this._messageSender) {
82
+ throw new Error(
83
+ 'WebsocketConnectionRepositoryMongo requires a messageSender for send functionality.\n' +
84
+ 'Pass one via constructor, e.g.:\n' +
85
+ ' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
86
+ ' new WebsocketConnectionRepositoryMongo(new ApiGatewayMessageSender())\n' +
87
+ 'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
88
+ );
89
+ }
90
+ const sender = this._messageSender;
91
+
77
92
  return connections.map((conn) => ({
78
93
  connectionId: conn.connectionId,
79
94
  send: async (data) => {
80
- const apigwManagementApi = new ApiGatewayManagementApiClient({
81
- endpoint: process.env.WEBSOCKET_API_ENDPOINT,
82
- });
83
-
84
95
  try {
85
- const command = new PostToConnectionCommand({
86
- ConnectionId: conn.connectionId,
87
- Data: JSON.stringify(data),
88
- });
89
- await apigwManagementApi.send(command);
96
+ await sender.send(
97
+ conn.connectionId,
98
+ data,
99
+ process.env.WEBSOCKET_API_ENDPOINT
100
+ );
90
101
  } catch (error) {
91
- if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
102
+ if (error instanceof StaleConnectionError) {
92
103
  console.log(
93
104
  `Stale connection ${conn.connectionId}`
94
105
  );
95
- // Delete stale connection
96
106
  await this.prisma.websocketConnection.deleteMany({
97
107
  where: { connectionId: conn.connectionId },
98
108
  });
@@ -1,8 +1,7 @@
1
1
  const { prisma } = require('../../database/prisma');
2
2
  const {
3
- ApiGatewayManagementApiClient,
4
- PostToConnectionCommand,
5
- } = require('@aws-sdk/client-apigatewaymanagementapi');
3
+ StaleConnectionError,
4
+ } = require('../websocket-message-sender-interface');
6
5
  const {
7
6
  WebsocketConnectionRepositoryInterface,
8
7
  } = require('./websocket-connection-repository-interface');
@@ -15,11 +14,17 @@ const {
15
14
  * - Uses Int IDs with autoincrement
16
15
  * - Requires ID conversion: String (app layer) ↔ Int (database)
17
16
  * - All returned IDs are converted to strings for application layer consistency
17
+ *
18
+ * BREAKING CHANGE (v3): A messageSender must be explicitly provided for
19
+ * WebSocket send functionality. For AWS API Gateway, pass
20
+ * `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
21
+ * See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
18
22
  */
19
23
  class WebsocketConnectionRepositoryPostgres extends WebsocketConnectionRepositoryInterface {
20
- constructor() {
24
+ constructor(messageSender = null) {
21
25
  super();
22
26
  this.prisma = prisma;
27
+ this._messageSender = messageSender;
23
28
  }
24
29
 
25
30
  /**
@@ -108,25 +113,31 @@ class WebsocketConnectionRepositoryPostgres extends WebsocketConnectionRepositor
108
113
  select: { connectionId: true },
109
114
  });
110
115
 
116
+ if (!this._messageSender) {
117
+ throw new Error(
118
+ 'WebsocketConnectionRepositoryPostgres requires a messageSender for send functionality.\n' +
119
+ 'Pass one via constructor, e.g.:\n' +
120
+ ' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
121
+ ' new WebsocketConnectionRepositoryPostgres(new ApiGatewayMessageSender())\n' +
122
+ 'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
123
+ );
124
+ }
125
+ const sender = this._messageSender;
126
+
111
127
  return connections.map((conn) => ({
112
128
  connectionId: conn.connectionId,
113
129
  send: async (data) => {
114
- const apigwManagementApi = new ApiGatewayManagementApiClient({
115
- endpoint: process.env.WEBSOCKET_API_ENDPOINT,
116
- });
117
-
118
130
  try {
119
- const command = new PostToConnectionCommand({
120
- ConnectionId: conn.connectionId,
121
- Data: JSON.stringify(data),
122
- });
123
- await apigwManagementApi.send(command);
131
+ await sender.send(
132
+ conn.connectionId,
133
+ data,
134
+ process.env.WEBSOCKET_API_ENDPOINT
135
+ );
124
136
  } catch (error) {
125
- if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
137
+ if (error instanceof StaleConnectionError) {
126
138
  console.log(
127
139
  `Stale connection ${conn.connectionId}`
128
140
  );
129
- // Delete stale connection
130
141
  await this.prisma.websocketConnection.deleteMany({
131
142
  where: { connectionId: conn.connectionId },
132
143
  });