@friggframework/core 2.0.0-next.8 → 2.0.0-next.80

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 (303) hide show
  1. package/CLAUDE.md +694 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +210 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +79 -8
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +268 -0
  30. package/database/encryption/field-encryption-service.js +226 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +222 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/package.json +183 -0
  69. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  70. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  71. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  72. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  73. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  74. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  75. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  76. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  77. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  78. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  79. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  80. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  81. package/generated/prisma-mongodb/schema.prisma +362 -0
  82. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  84. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  85. package/generated/prisma-mongodb/wasm.js +342 -0
  86. package/generated/prisma-postgresql/client.d.ts +1 -0
  87. package/generated/prisma-postgresql/client.js +4 -0
  88. package/generated/prisma-postgresql/default.d.ts +1 -0
  89. package/generated/prisma-postgresql/default.js +4 -0
  90. package/generated/prisma-postgresql/edge.d.ts +1 -0
  91. package/generated/prisma-postgresql/edge.js +357 -0
  92. package/generated/prisma-postgresql/index-browser.js +339 -0
  93. package/generated/prisma-postgresql/index.d.ts +25131 -0
  94. package/generated/prisma-postgresql/index.js +382 -0
  95. package/generated/prisma-postgresql/package.json +183 -0
  96. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  97. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  98. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  99. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  100. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  101. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  102. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  103. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  104. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  105. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  106. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  107. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  108. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  109. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  110. package/generated/prisma-postgresql/schema.prisma +345 -0
  111. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  112. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  113. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  114. package/generated/prisma-postgresql/wasm.js +364 -0
  115. package/handlers/WEBHOOKS.md +653 -0
  116. package/handlers/app-definition-loader.js +38 -0
  117. package/handlers/app-handler-helpers.js +57 -0
  118. package/handlers/backend-utils.js +262 -0
  119. package/handlers/database-migration-handler.js +227 -0
  120. package/handlers/integration-event-dispatcher.js +54 -0
  121. package/handlers/routers/HEALTHCHECK.md +342 -0
  122. package/handlers/routers/auth.js +15 -0
  123. package/handlers/routers/db-migration.handler.js +29 -0
  124. package/handlers/routers/db-migration.js +326 -0
  125. package/handlers/routers/health.js +516 -0
  126. package/handlers/routers/integration-defined-routers.js +45 -0
  127. package/handlers/routers/integration-webhook-routers.js +67 -0
  128. package/handlers/routers/user.js +63 -0
  129. package/handlers/routers/websocket.js +57 -0
  130. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  131. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  132. package/handlers/workers/db-migration.js +352 -0
  133. package/handlers/workers/dlq-processor.js +63 -0
  134. package/handlers/workers/integration-defined-workers.js +23 -0
  135. package/index.js +82 -46
  136. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  137. package/infrastructure/scheduler/index.js +33 -0
  138. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  139. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  140. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  141. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  142. package/integrations/index.js +12 -10
  143. package/integrations/integration-base.js +364 -55
  144. package/integrations/integration-router.js +375 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +243 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +90 -0
  160. package/integrations/repositories/process-repository-mongo.js +190 -0
  161. package/integrations/repositories/process-repository-postgres.js +217 -0
  162. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  163. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  164. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  165. package/integrations/use-cases/create-integration.js +83 -0
  166. package/integrations/use-cases/create-process.js +128 -0
  167. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  168. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  169. package/integrations/use-cases/get-integration-for-user.js +78 -0
  170. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  171. package/integrations/use-cases/get-integration-instance.js +83 -0
  172. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  173. package/integrations/use-cases/get-possible-integrations.js +27 -0
  174. package/integrations/use-cases/get-process.js +87 -0
  175. package/integrations/use-cases/index.js +19 -0
  176. package/integrations/use-cases/load-integration-context.js +71 -0
  177. package/integrations/use-cases/update-integration-messages.js +44 -0
  178. package/integrations/use-cases/update-integration-status.js +32 -0
  179. package/integrations/use-cases/update-integration.js +92 -0
  180. package/integrations/use-cases/update-process-metrics.js +201 -0
  181. package/integrations/use-cases/update-process-state.js +119 -0
  182. package/integrations/utils/map-integration-dto.js +37 -0
  183. package/jest-global-setup-noop.js +3 -0
  184. package/jest-global-teardown-noop.js +3 -0
  185. package/logs/logger.js +0 -4
  186. package/{module-plugin → modules}/index.js +0 -10
  187. package/modules/module-factory.js +56 -0
  188. package/modules/module.js +256 -0
  189. package/modules/repositories/module-repository-documentdb.js +335 -0
  190. package/modules/repositories/module-repository-factory.js +40 -0
  191. package/modules/repositories/module-repository-interface.js +129 -0
  192. package/modules/repositories/module-repository-mongo.js +408 -0
  193. package/modules/repositories/module-repository-postgres.js +453 -0
  194. package/modules/repositories/module-repository.js +345 -0
  195. package/modules/requester/api-key.js +52 -0
  196. package/modules/requester/oauth-2.js +396 -0
  197. package/{module-plugin → modules}/requester/requester.js +4 -2
  198. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  199. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  200. package/modules/tests/doubles/test-module-factory.js +16 -0
  201. package/modules/tests/doubles/test-module-repository.js +39 -0
  202. package/modules/use-cases/get-entities-for-user.js +32 -0
  203. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  204. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  205. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  206. package/modules/use-cases/get-module.js +74 -0
  207. package/modules/use-cases/process-authorization-callback.js +177 -0
  208. package/modules/use-cases/refresh-entity-options.js +72 -0
  209. package/modules/use-cases/test-module-auth.js +72 -0
  210. package/modules/utils/map-module-dto.js +18 -0
  211. package/package.json +82 -50
  212. package/prisma-mongodb/schema.prisma +362 -0
  213. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  214. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  215. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  216. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  217. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  218. package/prisma-postgresql/schema.prisma +345 -0
  219. package/queues/queuer-util.js +103 -21
  220. package/syncs/manager.js +468 -443
  221. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  222. package/syncs/repositories/sync-repository-factory.js +43 -0
  223. package/syncs/repositories/sync-repository-interface.js +109 -0
  224. package/syncs/repositories/sync-repository-mongo.js +239 -0
  225. package/syncs/repositories/sync-repository-postgres.js +319 -0
  226. package/syncs/sync.js +0 -1
  227. package/token/repositories/token-repository-documentdb.js +137 -0
  228. package/token/repositories/token-repository-factory.js +40 -0
  229. package/token/repositories/token-repository-interface.js +131 -0
  230. package/token/repositories/token-repository-mongo.js +219 -0
  231. package/token/repositories/token-repository-postgres.js +264 -0
  232. package/token/repositories/token-repository.js +219 -0
  233. package/types/associations/index.d.ts +0 -17
  234. package/types/core/index.d.ts +12 -4
  235. package/types/database/index.d.ts +10 -2
  236. package/types/encrypt/index.d.ts +5 -3
  237. package/types/integrations/index.d.ts +3 -8
  238. package/types/module-plugin/index.d.ts +17 -69
  239. package/types/syncs/index.d.ts +0 -17
  240. package/user/repositories/user-repository-documentdb.js +441 -0
  241. package/user/repositories/user-repository-factory.js +52 -0
  242. package/user/repositories/user-repository-interface.js +201 -0
  243. package/user/repositories/user-repository-mongo.js +308 -0
  244. package/user/repositories/user-repository-postgres.js +360 -0
  245. package/user/tests/doubles/test-user-repository.js +72 -0
  246. package/user/use-cases/authenticate-user.js +127 -0
  247. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  248. package/user/use-cases/create-individual-user.js +61 -0
  249. package/user/use-cases/create-organization-user.js +47 -0
  250. package/user/use-cases/create-token-for-user-id.js +30 -0
  251. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  252. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  253. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  254. package/user/use-cases/login-user.js +122 -0
  255. package/user/user.js +125 -0
  256. package/utils/backend-path.js +38 -0
  257. package/utils/index.js +6 -0
  258. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  259. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  260. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  261. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  262. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  263. package/websocket/repositories/websocket-connection-repository.js +161 -0
  264. package/assertions/is-equal.js +0 -17
  265. package/associations/model.js +0 -54
  266. package/database/models/IndividualUser.js +0 -76
  267. package/database/models/OrganizationUser.js +0 -29
  268. package/database/models/State.js +0 -9
  269. package/database/models/Token.js +0 -70
  270. package/database/models/UserModel.js +0 -7
  271. package/database/models/WebsocketConnection.js +0 -49
  272. package/database/mongo.js +0 -45
  273. package/database/mongoose.js +0 -5
  274. package/encrypt/Cryptor.test.js +0 -32
  275. package/encrypt/encrypt.js +0 -132
  276. package/encrypt/encrypt.test.js +0 -1069
  277. package/encrypt/test-encrypt.js +0 -107
  278. package/errors/base-error.test.js +0 -32
  279. package/errors/fetch-error.test.js +0 -79
  280. package/errors/halt-error.test.js +0 -11
  281. package/errors/validation-errors.test.js +0 -120
  282. package/integrations/create-frigg-backend.js +0 -31
  283. package/integrations/integration-factory.js +0 -251
  284. package/integrations/integration-mapping.js +0 -43
  285. package/integrations/integration-model.js +0 -46
  286. package/integrations/integration-user.js +0 -144
  287. package/integrations/test/integration-base.test.js +0 -144
  288. package/lambda/TimeoutCatcher.test.js +0 -68
  289. package/logs/logger.test.js +0 -76
  290. package/module-plugin/auther.js +0 -393
  291. package/module-plugin/credential.js +0 -22
  292. package/module-plugin/entity-manager.js +0 -70
  293. package/module-plugin/entity.js +0 -46
  294. package/module-plugin/manager.js +0 -169
  295. package/module-plugin/module-factory.js +0 -61
  296. package/module-plugin/requester/api-key.js +0 -36
  297. package/module-plugin/requester/oauth-2.js +0 -219
  298. package/module-plugin/requester/requester.test.js +0 -28
  299. package/module-plugin/test/auther.test.js +0 -97
  300. package/syncs/model.js +0 -62
  301. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  302. /package/{module-plugin → modules}/requester/basic.js +0 -0
  303. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,156 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ApiGatewayManagementApiClient,
4
+ PostToConnectionCommand,
5
+ } = require('@aws-sdk/client-apigatewaymanagementapi');
6
+ const {
7
+ WebsocketConnectionRepositoryInterface,
8
+ } = require('./websocket-connection-repository-interface');
9
+
10
+ /**
11
+ * MongoDB WebSocket Connection Repository Adapter
12
+ * Handles persistence of active WebSocket connections
13
+ *
14
+ * MongoDB-specific characteristics:
15
+ * - Uses String IDs (ObjectId)
16
+ * - No ID conversion needed (IDs are already strings)
17
+ * - AWS API Gateway Management API integration preserved
18
+ */
19
+ class WebsocketConnectionRepositoryMongo extends WebsocketConnectionRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ }
24
+
25
+ /**
26
+ * Create a new WebSocket connection record
27
+ * Replaces: WebsocketConnection.create({ connectionId })
28
+ *
29
+ * @param {string} connectionId - The WebSocket connection ID
30
+ * @returns {Promise<Object>} The created connection record with string IDs
31
+ */
32
+ async createConnection(connectionId) {
33
+ return await this.prisma.websocketConnection.create({
34
+ data: { connectionId },
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Delete a WebSocket connection record
40
+ * Replaces: WebsocketConnection.deleteOne({ connectionId })
41
+ *
42
+ * @param {string} connectionId - The WebSocket connection ID to delete
43
+ * @returns {Promise<Object>} The deletion result
44
+ */
45
+ async deleteConnection(connectionId) {
46
+ try {
47
+ await this.prisma.websocketConnection.delete({
48
+ where: { connectionId },
49
+ });
50
+ return { acknowledged: true, deletedCount: 1 };
51
+ } catch (error) {
52
+ if (error.code === 'P2025') {
53
+ // Record not found
54
+ return { acknowledged: true, deletedCount: 0 };
55
+ }
56
+ throw error;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Get all active WebSocket connections with send capability
62
+ * Replaces: WebsocketConnection.getActiveConnections()
63
+ *
64
+ * @returns {Promise<Array>} Array of active connection objects with send capability
65
+ */
66
+ async getActiveConnections() {
67
+ try {
68
+ // Return empty array if websockets are not configured
69
+ if (!process.env.WEBSOCKET_API_ENDPOINT) {
70
+ return [];
71
+ }
72
+
73
+ const connections = await this.prisma.websocketConnection.findMany({
74
+ select: { connectionId: true },
75
+ });
76
+
77
+ return connections.map((conn) => ({
78
+ connectionId: conn.connectionId,
79
+ send: async (data) => {
80
+ const apigwManagementApi = new ApiGatewayManagementApiClient({
81
+ endpoint: process.env.WEBSOCKET_API_ENDPOINT,
82
+ });
83
+
84
+ try {
85
+ const command = new PostToConnectionCommand({
86
+ ConnectionId: conn.connectionId,
87
+ Data: JSON.stringify(data),
88
+ });
89
+ await apigwManagementApi.send(command);
90
+ } catch (error) {
91
+ if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
92
+ console.log(
93
+ `Stale connection ${conn.connectionId}`
94
+ );
95
+ // Delete stale connection
96
+ await this.prisma.websocketConnection.deleteMany({
97
+ where: { connectionId: conn.connectionId },
98
+ });
99
+ } else {
100
+ throw error;
101
+ }
102
+ }
103
+ },
104
+ }));
105
+ } catch (error) {
106
+ console.error('Error getting active connections:', error);
107
+ throw error;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Find a connection by connection ID
113
+ * Replaces: WebsocketConnection.findOne({ connectionId })
114
+ *
115
+ * @param {string} connectionId - The WebSocket connection ID
116
+ * @returns {Promise<Object|null>} The connection record with string IDs or null
117
+ */
118
+ async findConnection(connectionId) {
119
+ return await this.prisma.websocketConnection.findFirst({
120
+ where: { connectionId },
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Find connection by internal ID
126
+ * @param {string} id - The internal connection ID
127
+ * @returns {Promise<Object|null>} The connection record with string IDs or null
128
+ */
129
+ async findConnectionById(id) {
130
+ return await this.prisma.websocketConnection.findUnique({
131
+ where: { id },
132
+ });
133
+ }
134
+
135
+ /**
136
+ * Get all connections
137
+ * @returns {Promise<Array>} Array of all connection records with string IDs
138
+ */
139
+ async getAllConnections() {
140
+ return await this.prisma.websocketConnection.findMany();
141
+ }
142
+
143
+ /**
144
+ * Delete all connections
145
+ * @returns {Promise<Object>} The deletion result
146
+ */
147
+ async deleteAllConnections() {
148
+ const result = await this.prisma.websocketConnection.deleteMany();
149
+ return {
150
+ acknowledged: true,
151
+ deletedCount: result.count,
152
+ };
153
+ }
154
+ }
155
+
156
+ module.exports = { WebsocketConnectionRepositoryMongo };
@@ -0,0 +1,196 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ApiGatewayManagementApiClient,
4
+ PostToConnectionCommand,
5
+ } = require('@aws-sdk/client-apigatewaymanagementapi');
6
+ const {
7
+ WebsocketConnectionRepositoryInterface,
8
+ } = require('./websocket-connection-repository-interface');
9
+
10
+ /**
11
+ * PostgreSQL WebSocket Connection Repository Adapter
12
+ * Handles persistence of active WebSocket connections
13
+ *
14
+ * PostgreSQL-specific characteristics:
15
+ * - Uses Int IDs with autoincrement
16
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
17
+ * - All returned IDs are converted to strings for application layer consistency
18
+ */
19
+ class WebsocketConnectionRepositoryPostgres extends WebsocketConnectionRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ }
24
+
25
+ /**
26
+ * Convert string ID to integer for PostgreSQL queries
27
+ * @private
28
+ * @param {string|number|null|undefined} id - ID to convert
29
+ * @returns {number|null|undefined} Integer ID or null/undefined
30
+ * @throws {Error} If ID cannot be converted to integer
31
+ */
32
+ _convertId(id) {
33
+ if (id === null || id === undefined) return id;
34
+ const parsed = parseInt(id, 10);
35
+ if (isNaN(parsed)) {
36
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
37
+ }
38
+ return parsed;
39
+ }
40
+
41
+ /**
42
+ * Convert connection object IDs to strings
43
+ * @private
44
+ * @param {Object|null} connection - Connection object from database
45
+ * @returns {Object|null} Connection with string IDs
46
+ */
47
+ _convertConnectionIds(connection) {
48
+ if (!connection) return connection;
49
+ return {
50
+ ...connection,
51
+ id: connection.id?.toString(),
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Create a new WebSocket connection record
57
+ * Replaces: WebsocketConnection.create({ connectionId })
58
+ *
59
+ * @param {string} connectionId - The WebSocket connection ID
60
+ * @returns {Promise<Object>} The created connection record with string IDs
61
+ */
62
+ async createConnection(connectionId) {
63
+ const connection = await this.prisma.websocketConnection.create({
64
+ data: { connectionId },
65
+ });
66
+ return this._convertConnectionIds(connection);
67
+ }
68
+
69
+ /**
70
+ * Delete a WebSocket connection record
71
+ * Replaces: WebsocketConnection.deleteOne({ connectionId })
72
+ *
73
+ * Note: connectionId is a string field in schema, not the primary key,
74
+ * so no conversion needed here.
75
+ *
76
+ * @param {string} connectionId - The WebSocket connection ID to delete
77
+ * @returns {Promise<Object>} The deletion result
78
+ */
79
+ async deleteConnection(connectionId) {
80
+ try {
81
+ await this.prisma.websocketConnection.delete({
82
+ where: { connectionId },
83
+ });
84
+ return { acknowledged: true, deletedCount: 1 };
85
+ } catch (error) {
86
+ if (error.code === 'P2025') {
87
+ // Record not found
88
+ return { acknowledged: true, deletedCount: 0 };
89
+ }
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get all active WebSocket connections with send capability
96
+ * Replaces: WebsocketConnection.getActiveConnections()
97
+ *
98
+ * @returns {Promise<Array>} Array of active connection objects with send capability
99
+ */
100
+ async getActiveConnections() {
101
+ try {
102
+ // Return empty array if websockets are not configured
103
+ if (!process.env.WEBSOCKET_API_ENDPOINT) {
104
+ return [];
105
+ }
106
+
107
+ const connections = await this.prisma.websocketConnection.findMany({
108
+ select: { connectionId: true },
109
+ });
110
+
111
+ return connections.map((conn) => ({
112
+ connectionId: conn.connectionId,
113
+ send: async (data) => {
114
+ const apigwManagementApi = new ApiGatewayManagementApiClient({
115
+ endpoint: process.env.WEBSOCKET_API_ENDPOINT,
116
+ });
117
+
118
+ try {
119
+ const command = new PostToConnectionCommand({
120
+ ConnectionId: conn.connectionId,
121
+ Data: JSON.stringify(data),
122
+ });
123
+ await apigwManagementApi.send(command);
124
+ } catch (error) {
125
+ if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
126
+ console.log(
127
+ `Stale connection ${conn.connectionId}`
128
+ );
129
+ // Delete stale connection
130
+ await this.prisma.websocketConnection.deleteMany({
131
+ where: { connectionId: conn.connectionId },
132
+ });
133
+ } else {
134
+ throw error;
135
+ }
136
+ }
137
+ },
138
+ }));
139
+ } catch (error) {
140
+ console.error('Error getting active connections:', error);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Find a connection by connection ID
147
+ * Replaces: WebsocketConnection.findOne({ connectionId })
148
+ *
149
+ * Note: connectionId is a string field, not the primary key
150
+ *
151
+ * @param {string} connectionId - The WebSocket connection ID
152
+ * @returns {Promise<Object|null>} The connection record with string IDs or null
153
+ */
154
+ async findConnection(connectionId) {
155
+ const connection = await this.prisma.websocketConnection.findFirst({
156
+ where: { connectionId },
157
+ });
158
+ return this._convertConnectionIds(connection);
159
+ }
160
+
161
+ /**
162
+ * Find connection by internal ID
163
+ * @param {string} id - The internal connection ID (string from application layer)
164
+ * @returns {Promise<Object|null>} The connection record with string IDs or null
165
+ */
166
+ async findConnectionById(id) {
167
+ const intId = this._convertId(id);
168
+ const connection = await this.prisma.websocketConnection.findUnique({
169
+ where: { id: intId },
170
+ });
171
+ return this._convertConnectionIds(connection);
172
+ }
173
+
174
+ /**
175
+ * Get all connections
176
+ * @returns {Promise<Array>} Array of all connection records with string IDs
177
+ */
178
+ async getAllConnections() {
179
+ const connections = await this.prisma.websocketConnection.findMany();
180
+ return connections.map((conn) => this._convertConnectionIds(conn));
181
+ }
182
+
183
+ /**
184
+ * Delete all connections
185
+ * @returns {Promise<Object>} The deletion result
186
+ */
187
+ async deleteAllConnections() {
188
+ const result = await this.prisma.websocketConnection.deleteMany();
189
+ return {
190
+ acknowledged: true,
191
+ deletedCount: result.count,
192
+ };
193
+ }
194
+ }
195
+
196
+ module.exports = { WebsocketConnectionRepositoryPostgres };
@@ -0,0 +1,161 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ApiGatewayManagementApiClient,
4
+ PostToConnectionCommand,
5
+ } = require('@aws-sdk/client-apigatewaymanagementapi');
6
+ const {
7
+ WebsocketConnectionRepositoryInterface,
8
+ } = require('./websocket-connection-repository-interface');
9
+
10
+ /**
11
+ * Prisma-based WebSocket Connection Repository
12
+ * Handles persistence of active WebSocket connections
13
+ *
14
+ * Works identically for both MongoDB and PostgreSQL:
15
+ * - MongoDB: String IDs with @db.ObjectId
16
+ * - PostgreSQL: Integer IDs with auto-increment
17
+ * - Both use same query patterns (no many-to-many differences)
18
+ *
19
+ * Migration from Mongoose:
20
+ * - Constructor injection of Prisma client
21
+ * - Static method getActiveConnections() → Instance method
22
+ * - AWS API Gateway Management API integration preserved
23
+ */
24
+ class WebsocketConnectionRepository extends WebsocketConnectionRepositoryInterface {
25
+ constructor(prismaClient = prisma) {
26
+ super();
27
+ this.prisma = prismaClient; // Allow injection for testing
28
+ }
29
+
30
+ /**
31
+ * Create a new WebSocket connection record
32
+ * Replaces: WebsocketConnection.create({ connectionId })
33
+ *
34
+ * @param {string} connectionId - The WebSocket connection ID
35
+ * @returns {Promise<Object>} The created connection record
36
+ */
37
+ async createConnection(connectionId) {
38
+ return await this.prisma.websocketConnection.create({
39
+ data: { connectionId },
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Delete a WebSocket connection record
45
+ * Replaces: WebsocketConnection.deleteOne({ connectionId })
46
+ *
47
+ * @param {string} connectionId - The WebSocket connection ID to delete
48
+ * @returns {Promise<Object>} The deletion result
49
+ */
50
+ async deleteConnection(connectionId) {
51
+ try {
52
+ await this.prisma.websocketConnection.delete({
53
+ where: { connectionId },
54
+ });
55
+ return { acknowledged: true, deletedCount: 1 };
56
+ } catch (error) {
57
+ if (error.code === 'P2025') {
58
+ // Record not found
59
+ return { acknowledged: true, deletedCount: 0 };
60
+ }
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Get all active WebSocket connections with send capability
67
+ * Replaces: WebsocketConnection.getActiveConnections()
68
+ *
69
+ * @returns {Promise<Array>} Array of active connection objects with send capability
70
+ */
71
+ async getActiveConnections() {
72
+ try {
73
+ // Return empty array if websockets are not configured
74
+ if (!process.env.WEBSOCKET_API_ENDPOINT) {
75
+ return [];
76
+ }
77
+
78
+ const connections = await this.prisma.websocketConnection.findMany({
79
+ select: { connectionId: true },
80
+ });
81
+
82
+ return connections.map((conn) => ({
83
+ connectionId: conn.connectionId,
84
+ send: async (data) => {
85
+ const apigwManagementApi = new ApiGatewayManagementApiClient({
86
+ endpoint: process.env.WEBSOCKET_API_ENDPOINT,
87
+ });
88
+
89
+ try {
90
+ const command = new PostToConnectionCommand({
91
+ ConnectionId: conn.connectionId,
92
+ Data: JSON.stringify(data),
93
+ });
94
+ await apigwManagementApi.send(command);
95
+ } catch (error) {
96
+ if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
97
+ console.log(
98
+ `Stale connection ${conn.connectionId}`
99
+ );
100
+ // Delete stale connection
101
+ await this.prisma.websocketConnection.deleteMany({
102
+ where: { connectionId: conn.connectionId },
103
+ });
104
+ } else {
105
+ throw error;
106
+ }
107
+ }
108
+ },
109
+ }));
110
+ } catch (error) {
111
+ console.error('Error getting active connections:', error);
112
+ throw error;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Find a connection by ID
118
+ * Replaces: WebsocketConnection.findOne({ connectionId })
119
+ *
120
+ * @param {string} connectionId - The WebSocket connection ID
121
+ * @returns {Promise<Object|null>} The connection record or null
122
+ */
123
+ async findConnection(connectionId) {
124
+ return await this.prisma.websocketConnection.findFirst({
125
+ where: { connectionId },
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Find connection by internal ID
131
+ * @param {string} id - The internal connection ID
132
+ * @returns {Promise<Object|null>} The connection record or null
133
+ */
134
+ async findConnectionById(id) {
135
+ return await this.prisma.websocketConnection.findUnique({
136
+ where: { id },
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Get all connections
142
+ * @returns {Promise<Array>} Array of all connection records
143
+ */
144
+ async getAllConnections() {
145
+ return await this.prisma.websocketConnection.findMany();
146
+ }
147
+
148
+ /**
149
+ * Delete all connections
150
+ * @returns {Promise<Object>} The deletion result
151
+ */
152
+ async deleteAllConnections() {
153
+ const result = await this.prisma.websocketConnection.deleteMany();
154
+ return {
155
+ acknowledged: true,
156
+ deletedCount: result.count,
157
+ };
158
+ }
159
+ }
160
+
161
+ module.exports = { WebsocketConnectionRepository };
@@ -1,17 +0,0 @@
1
- const expectShallowEqualDbObject = (modelObject, compareObject) => {
2
- for (const key in compareObject) {
3
- let objVal = modelObject[key];
4
-
5
- if (objVal instanceof Date) {
6
- objVal = objVal.toISOString();
7
- } else if (objVal instanceof mongoose.Types.ObjectId) {
8
- objVal = objVal._id.toString();
9
- }
10
-
11
- expect(compareObject[key]).toBe(objVal);
12
- }
13
- };
14
-
15
- // TODO not sure how much this is needed, but could rewrite with _.isEqualWith for deep equality with custom checks.
16
-
17
- module.exports = { expectShallowEqualDbObject };
@@ -1,54 +0,0 @@
1
- const mongoose = require("mongoose");
2
-
3
- const schema = new mongoose.Schema({
4
- integration: {
5
- type: mongoose.Schema.Types.ObjectId,
6
- ref: "Integration",
7
- required: true,
8
- },
9
- name: { type: String, required: true },
10
- type: {
11
- type: String,
12
- enum: ["ONE_TO_MANY", "ONE_TO_ONE", "MANY_TO_ONE"],
13
- required: true,
14
- },
15
- primaryObject: { type: String, required: true },
16
- objects: [
17
- {
18
- entity: {
19
- type: mongoose.Schema.Types.ObjectId,
20
- ref: "Entity",
21
- required: true,
22
- },
23
- objectType: { type: String, required: true },
24
- objId: { type: String, required: true },
25
- metadata: { type: Object, required: false },
26
- },
27
- ],
28
- });
29
-
30
- schema.statics({
31
- addAssociation: async function (id, object) {
32
- return this.update({ _id: id }, { $push: { objects: object } });
33
- },
34
- findAssociation: async function (name, dataIdentifierHash) {
35
- const syncList = await this.list({
36
- name: name,
37
- "dataIdentifiers.hash": dataIdentifierHash,
38
- });
39
-
40
- if (syncList.length === 1) {
41
- return syncList[0];
42
- } else if (syncList.length === 0) {
43
- return null;
44
- } else {
45
- throw new Error(
46
- `there are multiple sync objects with the name ${name}, for entities [${entities}]`
47
- );
48
- }
49
- },
50
- });
51
-
52
- const Association =
53
- mongoose.models.Association || mongoose.model("Association", schema);
54
- module.exports = { Association };
@@ -1,76 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
- const bcrypt = require('bcryptjs');
3
- const { UserModel: Parent } = require('./UserModel');
4
-
5
- const collectionName = 'IndividualUser';
6
-
7
- const schema = new mongoose.Schema({
8
- email: { type: String },
9
- username: { type: String, unique: true },
10
- hashword: { type: String },
11
- appUserId: { type: String },
12
- organizationUser: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
13
- });
14
-
15
- schema.pre('save', async function () {
16
- if (this.hashword) {
17
- this.hashword = await bcrypt.hashSync(
18
- this.hashword,
19
- parseInt(this.schema.statics.decimals)
20
- )
21
- }
22
- })
23
-
24
- schema.static({
25
- decimals: 10,
26
- update: async function (id, options) {
27
- if ('password' in options) {
28
- options.hashword = await bcrypt.hashSync(
29
- options.password,
30
- parseInt(this.decimals)
31
- );
32
- delete options.password;
33
- }
34
- return this.findOneAndUpdate(
35
- {_id: id},
36
- options,
37
- {new: true, useFindAndModify: true}
38
- );
39
- },
40
- getUserByUsername: async function (username) {
41
- let getByUser;
42
- try{
43
- getByUser = await this.find({username});
44
- } catch (e) {
45
- console.log('oops')
46
- }
47
-
48
- if (getByUser.length > 1) {
49
- throw new Error(
50
- 'Unique username or email? Please reach out to our developers'
51
- );
52
- }
53
-
54
- if (getByUser.length === 1) {
55
- return getByUser[0];
56
- }
57
- },
58
- getUserByAppUserId: async function (appUserId) {
59
- const getByUser = await this.find({ appUserId });
60
-
61
- if (getByUser.length > 1) {
62
- throw new Error(
63
- 'Supposedly using a unique appUserId? Please reach out to our developers'
64
- );
65
- }
66
-
67
-
68
- if (getByUser.length === 1) {
69
- return getByUser[0];
70
- }
71
- }
72
- })
73
-
74
- const IndividualUser = Parent.discriminators?.IndividualUser || Parent.discriminator(collectionName, schema);
75
-
76
- module.exports = {IndividualUser};
@@ -1,29 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
- const { UserModel: Parent } = require('./UserModel');
3
-
4
- const collectionName = 'OrganizationUser';
5
-
6
- const schema = new mongoose.Schema({
7
- appOrgId: { type: String, required: true, unique: true },
8
- name: { type: String },
9
- });
10
-
11
- schema.static({
12
- getUserByAppOrgId: async function (appOrgId) {
13
- const getByUser = await this.find({ appOrgId });
14
-
15
- if (getByUser.length > 1) {
16
- throw new Error(
17
- 'Supposedly using a unique appOrgId? Please reach out to our developers'
18
- );
19
- }
20
-
21
- if (getByUser.length === 1) {
22
- return getByUser[0];
23
- }
24
- }
25
- })
26
-
27
- const OrganizationUser = Parent.discriminators?.OrganizationUser || Parent.discriminator(collectionName, schema);
28
-
29
- module.exports = {OrganizationUser};
@@ -1,9 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
-
3
- const schema = new mongoose.Schema({
4
- state: { type: mongoose.Schema.Types.Mixed }
5
- });
6
-
7
- const State = mongoose.models.State || mongoose.model('State', schema);
8
-
9
- module.exports = { State };