@friggframework/core 2.0.0-next.5 → 2.0.0-next.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +179 -0
  7. package/application/commands/user-commands.js +213 -0
  8. package/application/index.js +69 -0
  9. package/core/CLAUDE.md +690 -0
  10. package/core/Worker.js +8 -21
  11. package/core/create-handler.js +2 -7
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +307 -0
  15. package/credential/repositories/credential-repository-postgres.js +313 -0
  16. package/credential/repositories/credential-repository.js +302 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  20. package/database/adapters/lambda-invoker.js +97 -0
  21. package/database/config.js +154 -0
  22. package/database/encryption/README.md +684 -0
  23. package/database/encryption/encryption-schema-registry.js +141 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/logger.js +79 -0
  26. package/database/encryption/prisma-encryption-extension.js +222 -0
  27. package/database/index.js +25 -12
  28. package/database/models/WebsocketConnection.js +16 -10
  29. package/database/models/readme.md +1 -0
  30. package/database/prisma.js +222 -0
  31. package/database/repositories/health-check-repository-factory.js +43 -0
  32. package/database/repositories/health-check-repository-interface.js +87 -0
  33. package/database/repositories/health-check-repository-mongodb.js +91 -0
  34. package/database/repositories/health-check-repository-postgres.js +82 -0
  35. package/database/repositories/health-check-repository.js +108 -0
  36. package/database/repositories/migration-status-repository-s3.js +137 -0
  37. package/database/use-cases/check-database-health-use-case.js +29 -0
  38. package/database/use-cases/check-database-state-use-case.js +81 -0
  39. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  40. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  41. package/database/use-cases/get-migration-status-use-case.js +93 -0
  42. package/database/use-cases/run-database-migration-use-case.js +137 -0
  43. package/database/use-cases/test-encryption-use-case.js +253 -0
  44. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  45. package/database/utils/mongodb-collection-utils.js +91 -0
  46. package/database/utils/mongodb-schema-init.js +106 -0
  47. package/database/utils/prisma-runner.js +400 -0
  48. package/database/utils/prisma-schema-parser.js +182 -0
  49. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  50. package/encrypt/Cryptor.js +34 -168
  51. package/encrypt/index.js +1 -2
  52. package/encrypt/test-encrypt.js +0 -2
  53. package/generated/prisma-mongodb/client.d.ts +1 -0
  54. package/generated/prisma-mongodb/client.js +4 -0
  55. package/generated/prisma-mongodb/default.d.ts +1 -0
  56. package/generated/prisma-mongodb/default.js +4 -0
  57. package/generated/prisma-mongodb/edge.d.ts +1 -0
  58. package/generated/prisma-mongodb/edge.js +334 -0
  59. package/generated/prisma-mongodb/index-browser.js +316 -0
  60. package/generated/prisma-mongodb/index.d.ts +22898 -0
  61. package/generated/prisma-mongodb/index.js +359 -0
  62. package/generated/prisma-mongodb/package.json +183 -0
  63. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  64. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  65. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  66. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  67. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  68. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  69. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  70. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  71. package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
  72. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  73. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  74. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  75. package/generated/prisma-mongodb/schema.prisma +362 -0
  76. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  77. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  78. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  79. package/generated/prisma-mongodb/wasm.js +341 -0
  80. package/generated/prisma-postgresql/client.d.ts +1 -0
  81. package/generated/prisma-postgresql/client.js +4 -0
  82. package/generated/prisma-postgresql/default.d.ts +1 -0
  83. package/generated/prisma-postgresql/default.js +4 -0
  84. package/generated/prisma-postgresql/edge.d.ts +1 -0
  85. package/generated/prisma-postgresql/edge.js +356 -0
  86. package/generated/prisma-postgresql/index-browser.js +338 -0
  87. package/generated/prisma-postgresql/index.d.ts +25072 -0
  88. package/generated/prisma-postgresql/index.js +381 -0
  89. package/generated/prisma-postgresql/package.json +183 -0
  90. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  91. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  92. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  93. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  94. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  95. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  96. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  97. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  98. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  99. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  100. package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
  101. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  102. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  103. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  104. package/generated/prisma-postgresql/schema.prisma +345 -0
  105. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  106. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  107. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  108. package/generated/prisma-postgresql/wasm.js +363 -0
  109. package/handlers/WEBHOOKS.md +653 -0
  110. package/handlers/app-definition-loader.js +38 -0
  111. package/handlers/app-handler-helpers.js +56 -0
  112. package/handlers/backend-utils.js +180 -0
  113. package/handlers/database-migration-handler.js +227 -0
  114. package/handlers/integration-event-dispatcher.js +54 -0
  115. package/handlers/routers/HEALTHCHECK.md +342 -0
  116. package/handlers/routers/auth.js +15 -0
  117. package/handlers/routers/db-migration.handler.js +29 -0
  118. package/handlers/routers/db-migration.js +256 -0
  119. package/handlers/routers/health.js +519 -0
  120. package/handlers/routers/integration-defined-routers.js +45 -0
  121. package/handlers/routers/integration-webhook-routers.js +67 -0
  122. package/handlers/routers/user.js +63 -0
  123. package/handlers/routers/websocket.js +57 -0
  124. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  125. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  126. package/handlers/workers/db-migration.js +352 -0
  127. package/handlers/workers/integration-defined-workers.js +27 -0
  128. package/index.js +77 -22
  129. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  130. package/integrations/index.js +12 -10
  131. package/integrations/integration-base.js +296 -54
  132. package/integrations/integration-router.js +381 -182
  133. package/integrations/options.js +1 -1
  134. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  135. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  136. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  137. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  138. package/integrations/repositories/integration-mapping-repository.js +156 -0
  139. package/integrations/repositories/integration-repository-factory.js +44 -0
  140. package/integrations/repositories/integration-repository-interface.js +127 -0
  141. package/integrations/repositories/integration-repository-mongo.js +303 -0
  142. package/integrations/repositories/integration-repository-postgres.js +352 -0
  143. package/integrations/repositories/process-repository-factory.js +46 -0
  144. package/integrations/repositories/process-repository-interface.js +90 -0
  145. package/integrations/repositories/process-repository-mongo.js +190 -0
  146. package/integrations/repositories/process-repository-postgres.js +217 -0
  147. package/integrations/tests/doubles/dummy-integration-class.js +83 -0
  148. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  149. package/integrations/use-cases/create-integration.js +83 -0
  150. package/integrations/use-cases/create-process.js +128 -0
  151. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  152. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  153. package/integrations/use-cases/get-integration-for-user.js +78 -0
  154. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  155. package/integrations/use-cases/get-integration-instance.js +83 -0
  156. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  157. package/integrations/use-cases/get-possible-integrations.js +27 -0
  158. package/integrations/use-cases/get-process.js +87 -0
  159. package/integrations/use-cases/index.js +19 -0
  160. package/integrations/use-cases/load-integration-context.js +71 -0
  161. package/integrations/use-cases/update-integration-messages.js +44 -0
  162. package/integrations/use-cases/update-integration-status.js +32 -0
  163. package/integrations/use-cases/update-integration.js +93 -0
  164. package/integrations/use-cases/update-process-metrics.js +201 -0
  165. package/integrations/use-cases/update-process-state.js +119 -0
  166. package/integrations/utils/map-integration-dto.js +36 -0
  167. package/jest-global-setup-noop.js +3 -0
  168. package/jest-global-teardown-noop.js +3 -0
  169. package/logs/logger.js +0 -4
  170. package/{module-plugin → modules}/entity.js +1 -1
  171. package/{module-plugin → modules}/index.js +0 -8
  172. package/modules/module-factory.js +56 -0
  173. package/modules/module.js +221 -0
  174. package/modules/repositories/module-repository-factory.js +33 -0
  175. package/modules/repositories/module-repository-interface.js +129 -0
  176. package/modules/repositories/module-repository-mongo.js +377 -0
  177. package/modules/repositories/module-repository-postgres.js +426 -0
  178. package/modules/repositories/module-repository.js +316 -0
  179. package/{module-plugin → modules}/requester/requester.js +1 -0
  180. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  181. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  182. package/modules/tests/doubles/test-module-factory.js +16 -0
  183. package/modules/tests/doubles/test-module-repository.js +39 -0
  184. package/modules/use-cases/get-entities-for-user.js +32 -0
  185. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  186. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  187. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  188. package/modules/use-cases/get-module.js +55 -0
  189. package/modules/use-cases/process-authorization-callback.js +122 -0
  190. package/modules/use-cases/refresh-entity-options.js +59 -0
  191. package/modules/use-cases/test-module-auth.js +55 -0
  192. package/modules/utils/map-module-dto.js +18 -0
  193. package/package.json +82 -50
  194. package/prisma-mongodb/schema.prisma +362 -0
  195. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  196. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  197. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  198. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  199. package/prisma-postgresql/schema.prisma +345 -0
  200. package/queues/queuer-util.js +28 -15
  201. package/syncs/manager.js +468 -443
  202. package/syncs/repositories/sync-repository-factory.js +38 -0
  203. package/syncs/repositories/sync-repository-interface.js +109 -0
  204. package/syncs/repositories/sync-repository-mongo.js +239 -0
  205. package/syncs/repositories/sync-repository-postgres.js +319 -0
  206. package/syncs/sync.js +0 -1
  207. package/token/repositories/token-repository-factory.js +33 -0
  208. package/token/repositories/token-repository-interface.js +131 -0
  209. package/token/repositories/token-repository-mongo.js +212 -0
  210. package/token/repositories/token-repository-postgres.js +257 -0
  211. package/token/repositories/token-repository.js +219 -0
  212. package/types/core/index.d.ts +2 -2
  213. package/types/integrations/index.d.ts +2 -6
  214. package/types/module-plugin/index.d.ts +5 -59
  215. package/types/syncs/index.d.ts +0 -2
  216. package/user/repositories/user-repository-factory.js +46 -0
  217. package/user/repositories/user-repository-interface.js +198 -0
  218. package/user/repositories/user-repository-mongo.js +291 -0
  219. package/user/repositories/user-repository-postgres.js +350 -0
  220. package/user/tests/doubles/test-user-repository.js +72 -0
  221. package/user/use-cases/authenticate-user.js +127 -0
  222. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  223. package/user/use-cases/create-individual-user.js +61 -0
  224. package/user/use-cases/create-organization-user.js +47 -0
  225. package/user/use-cases/create-token-for-user-id.js +30 -0
  226. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  227. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  228. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  229. package/user/use-cases/login-user.js +122 -0
  230. package/user/user.js +93 -0
  231. package/utils/backend-path.js +38 -0
  232. package/utils/index.js +6 -0
  233. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  234. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  235. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  236. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  237. package/websocket/repositories/websocket-connection-repository.js +161 -0
  238. package/database/models/State.js +0 -9
  239. package/database/models/Token.js +0 -70
  240. package/database/mongo.js +0 -45
  241. package/encrypt/Cryptor.test.js +0 -32
  242. package/encrypt/encrypt.js +0 -132
  243. package/encrypt/encrypt.test.js +0 -1069
  244. package/errors/base-error.test.js +0 -32
  245. package/errors/fetch-error.test.js +0 -79
  246. package/errors/halt-error.test.js +0 -11
  247. package/errors/validation-errors.test.js +0 -120
  248. package/integrations/create-frigg-backend.js +0 -31
  249. package/integrations/integration-factory.js +0 -251
  250. package/integrations/integration-mapping.js +0 -43
  251. package/integrations/integration-model.js +0 -46
  252. package/integrations/integration-user.js +0 -144
  253. package/integrations/test/integration-base.test.js +0 -144
  254. package/lambda/TimeoutCatcher.test.js +0 -68
  255. package/logs/logger.test.js +0 -76
  256. package/module-plugin/auther.js +0 -393
  257. package/module-plugin/credential.js +0 -22
  258. package/module-plugin/entity-manager.js +0 -70
  259. package/module-plugin/manager.js +0 -169
  260. package/module-plugin/module-factory.js +0 -61
  261. package/module-plugin/requester/requester.test.js +0 -28
  262. package/module-plugin/test/auther.test.js +0 -97
  263. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  264. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  265. /package/{module-plugin → modules}/requester/basic.js +0 -0
  266. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  267. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,57 @@
1
+ const { createHandler } = require('@friggframework/core');
2
+ const { createWebsocketConnectionRepository } = require('../../database/websocket-connection-repository-factory');
3
+
4
+ const websocketConnectionRepository = createWebsocketConnectionRepository();
5
+
6
+ const handleWebSocketConnection = async (event, context) => {
7
+ // Handle different WebSocket events
8
+ switch (event.requestContext.eventType) {
9
+ case 'CONNECT':
10
+ // Handle new connection
11
+ try {
12
+ const connectionId = event.requestContext.connectionId;
13
+ await websocketConnectionRepository.createConnection(connectionId);
14
+ console.log(`Stored new connection: ${connectionId}`);
15
+ return { statusCode: 200, body: 'Connected.' };
16
+ } catch (error) {
17
+ console.error('Error storing connection:', error);
18
+ return { statusCode: 500, body: 'Error connecting.' };
19
+ }
20
+
21
+ case 'DISCONNECT':
22
+ // Handle disconnection
23
+ try {
24
+ const connectionId = event.requestContext.connectionId;
25
+ await websocketConnectionRepository.deleteConnection(connectionId);
26
+ console.log(`Removed connection: ${connectionId}`);
27
+ return { statusCode: 200, body: 'Disconnected.' };
28
+ } catch (error) {
29
+ console.error('Error removing connection:', error);
30
+ return { statusCode: 500, body: 'Error disconnecting.' };
31
+ }
32
+
33
+ case 'MESSAGE':
34
+ // Handle incoming message
35
+ const message = JSON.parse(event.body);
36
+ console.log('Received message:', message);
37
+
38
+ // Process the message and send a response
39
+ const responseMessage = { message: 'Message received' };
40
+ return {
41
+ statusCode: 200,
42
+ body: JSON.stringify(responseMessage),
43
+ };
44
+
45
+ default:
46
+ return { statusCode: 400, body: 'Unhandled event type.' };
47
+ }
48
+ };
49
+
50
+ const handler = createHandler({
51
+ eventName: 'WebSocket Event',
52
+ method: handleWebSocketConnection,
53
+ shouldUseDatabase: true, // Set to true as we're using the database
54
+ isUserFacingResponse: true, // This is a server-to-server response
55
+ });
56
+
57
+ module.exports = { handler };
@@ -0,0 +1,81 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ class CheckExternalApisHealthUseCase {
5
+ constructor({ apis = null } = {}) {
6
+ this.apis = apis || [
7
+ { name: 'github', url: 'https://api.github.com/status' },
8
+ { name: 'npm', url: 'https://registry.npmjs.org' },
9
+ ];
10
+ }
11
+
12
+ async execute() {
13
+ const results = await Promise.all(
14
+ this.apis.map((api) =>
15
+ this._checkExternalAPI(api.url).then((result) => ({
16
+ name: api.name,
17
+ ...result,
18
+ }))
19
+ )
20
+ );
21
+
22
+ const apiStatuses = {};
23
+ let allReachable = true;
24
+
25
+ results.forEach(({ name, ...checkResult }) => {
26
+ apiStatuses[name] = checkResult;
27
+ if (!checkResult.reachable) {
28
+ allReachable = false;
29
+ }
30
+ });
31
+
32
+ return { apiStatuses, allReachable };
33
+ }
34
+
35
+ _checkExternalAPI(url, timeout = 5000) {
36
+ return new Promise((resolve) => {
37
+ const protocol = url.startsWith('https:') ? https : http;
38
+ const startTime = Date.now();
39
+
40
+ try {
41
+ const request = protocol.get(url, { timeout }, (res) => {
42
+ const responseTime = Date.now() - startTime;
43
+ resolve({
44
+ status: 'healthy',
45
+ statusCode: res.statusCode,
46
+ responseTime,
47
+ reachable: res.statusCode < 500,
48
+ });
49
+ });
50
+
51
+ request.on('error', (error) => {
52
+ resolve({
53
+ status: 'unhealthy',
54
+ error: error.message,
55
+ responseTime: Date.now() - startTime,
56
+ reachable: false,
57
+ });
58
+ });
59
+
60
+ request.on('timeout', () => {
61
+ request.destroy();
62
+ resolve({
63
+ status: 'timeout',
64
+ error: 'Request timeout',
65
+ responseTime: timeout,
66
+ reachable: false,
67
+ });
68
+ });
69
+ } catch (error) {
70
+ resolve({
71
+ status: 'error',
72
+ error: error.message,
73
+ responseTime: Date.now() - startTime,
74
+ reachable: false,
75
+ });
76
+ }
77
+ });
78
+ }
79
+ }
80
+
81
+ module.exports = { CheckExternalApisHealthUseCase };
@@ -0,0 +1,44 @@
1
+ class CheckIntegrationsHealthUseCase {
2
+ constructor({ moduleFactory, integrationClasses }) {
3
+ this.moduleFactory = moduleFactory;
4
+ this.integrationClasses = integrationClasses;
5
+ }
6
+
7
+ execute() {
8
+ const moduleDefinitions = (this.moduleFactory && this.moduleFactory.moduleDefinitions)
9
+ ? this.moduleFactory.moduleDefinitions
10
+ : [];
11
+
12
+ const integrationClasses = Array.isArray(this.integrationClasses)
13
+ ? this.integrationClasses
14
+ : [];
15
+
16
+ // Extract module names from definitions
17
+ const moduleTypes = Array.isArray(moduleDefinitions)
18
+ ? moduleDefinitions.map(def => def.moduleName || def.name || def.label || 'Unknown')
19
+ : [];
20
+
21
+ // Extract integration names from classes
22
+ const integrationNames = integrationClasses.map(IntegrationClass => {
23
+ try {
24
+ return IntegrationClass.Definition?.name || IntegrationClass.name || 'Unknown';
25
+ } catch {
26
+ return 'Unknown';
27
+ }
28
+ });
29
+
30
+ return {
31
+ status: 'healthy',
32
+ modules: {
33
+ count: moduleTypes.length,
34
+ available: moduleTypes,
35
+ },
36
+ integrations: {
37
+ count: integrationNames.length,
38
+ available: integrationNames,
39
+ },
40
+ };
41
+ }
42
+ }
43
+
44
+ module.exports = { CheckIntegrationsHealthUseCase };
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Database Migration Lambda Handler
3
+ *
4
+ * Lambda function that runs Prisma database migrations from within the VPC,
5
+ * enabling CI/CD pipelines to migrate databases without requiring public access.
6
+ *
7
+ * This handler uses the prisma-runner utilities from @friggframework/core,
8
+ * ensuring consistency with the `frigg db:setup` command.
9
+ *
10
+ * Environment Variables Required:
11
+ * - DATABASE_URL: PostgreSQL connection string (automatically set from Secrets Manager)
12
+ * - DB_TYPE: Database type ('postgresql' or 'mongodb')
13
+ * - STAGE: Deployment stage (determines migration command: 'dev' or 'deploy')
14
+ *
15
+ * Invocation:
16
+ * aws lambda invoke \
17
+ * --function-name my-app-production-dbMigrate \
18
+ * --region us-east-1 \
19
+ * response.json
20
+ *
21
+ * Success Response:
22
+ * {
23
+ * "statusCode": 200,
24
+ * "body": {
25
+ * "success": true,
26
+ * "message": "Database migration completed successfully",
27
+ * "dbType": "postgresql",
28
+ * "stage": "production",
29
+ * "migrationCommand": "deploy"
30
+ * }
31
+ * }
32
+ *
33
+ * Error Response:
34
+ * {
35
+ * "statusCode": 500,
36
+ * "body": {
37
+ * "success": false,
38
+ * "error": "Migration failed: ...",
39
+ * "stack": "Error: ..."
40
+ * }
41
+ * }
42
+ */
43
+
44
+ const {
45
+ RunDatabaseMigrationUseCase,
46
+ MigrationError,
47
+ ValidationError,
48
+ } = require('../../database/use-cases/run-database-migration-use-case');
49
+ const {
50
+ CheckDatabaseStateUseCase,
51
+ } = require('../../database/use-cases/check-database-state-use-case');
52
+ const {
53
+ MigrationStatusRepositoryS3,
54
+ } = require('../../database/repositories/migration-status-repository-s3');
55
+
56
+ // Inject prisma-runner as dependency
57
+ const prismaRunner = require('../../database/utils/prisma-runner');
58
+
59
+ // Use S3 repository for migration status tracking (no User table dependency)
60
+ const bucketName = process.env.S3_BUCKET_NAME || process.env.MIGRATION_STATUS_BUCKET;
61
+ const migrationStatusRepository = new MigrationStatusRepositoryS3(bucketName);
62
+
63
+ /**
64
+ * Sanitizes error messages to prevent credential leaks
65
+ * @param {string} errorMessage - Error message that might contain credentials
66
+ * @returns {string} Sanitized error message
67
+ */
68
+ function sanitizeError(errorMessage) {
69
+ if (!errorMessage) return 'Unknown error';
70
+
71
+ return String(errorMessage)
72
+ // Remove PostgreSQL connection strings
73
+ .replace(/postgresql:\/\/[^@\s]+@[^\s/]+/gi, 'postgresql://***:***@***')
74
+ // Remove MongoDB connection strings
75
+ .replace(/mongodb(\+srv)?:\/\/[^@\s]+@[^\s/]+/gi, 'mongodb$1://***:***@***')
76
+ // Remove password parameters
77
+ .replace(/password[=:]\s*[^\s,;)]+/gi, 'password=***')
78
+ // Remove API keys
79
+ .replace(/apikey[=:]\s*[^\s,;)]+/gi, 'apikey=***')
80
+ .replace(/api[_-]?key[=:]\s*[^\s,;)]+/gi, 'api_key=***')
81
+ // Remove tokens
82
+ .replace(/token[=:]\s*[^\s,;)]+/gi, 'token=***')
83
+ .replace(/bearer\s+[^\s,;)]+/gi, 'bearer ***');
84
+ }
85
+
86
+ /**
87
+ * Sanitizes DATABASE_URL for safe logging
88
+ * @param {string} url - Database URL
89
+ * @returns {string} Sanitized URL
90
+ */
91
+ function sanitizeDatabaseUrl(url) {
92
+ if (!url) return '';
93
+
94
+ // Replace credentials in connection string
95
+ return url.replace(/(:\/\/)([^:]+):([^@]+)@/, '$1***:***@');
96
+ }
97
+
98
+ /**
99
+ * Extract migration parameters from SQS event or direct invocation
100
+ * @param {Object} event - Lambda event (SQS or direct)
101
+ * @returns {Object} Extracted parameters { migrationId, dbType, stage }
102
+ */
103
+ function extractMigrationParams(event) {
104
+ let migrationId = null;
105
+ let stage = null;
106
+
107
+ // Migration infrastructure is PostgreSQL-only, so hardcode dbType
108
+ const dbType = 'postgresql';
109
+
110
+ // Check if this is an SQS event
111
+ if (event.Records && event.Records.length > 0) {
112
+ // SQS event - extract from message body
113
+ const message = JSON.parse(event.Records[0].body);
114
+ migrationId = message.migrationId;
115
+ stage = message.stage;
116
+
117
+ console.log('SQS event detected');
118
+ console.log(` Migration ID: ${migrationId}`);
119
+ console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
120
+ console.log(` Stage: ${stage}`);
121
+ } else {
122
+ // Direct invocation - use event properties or environment variables
123
+ migrationId = event.migrationId || null;
124
+ stage = event.stage || process.env.STAGE || 'production';
125
+
126
+ console.log('Direct invocation detected');
127
+ if (migrationId) {
128
+ console.log(` Migration ID: ${migrationId}`);
129
+ }
130
+ console.log(` DB Type: ${dbType} (hardcoded - PostgreSQL-only)`);
131
+ console.log(` Stage: ${stage}`);
132
+ }
133
+
134
+ return { migrationId, dbType, stage };
135
+ }
136
+
137
+ /**
138
+ * Lambda handler entry point
139
+ * @param {Object} event - Lambda event (SQS message or direct invocation)
140
+ * @param {Object} context - Lambda context (contains AWS request ID, timeout info)
141
+ * @returns {Promise<Object>} Response with statusCode and body
142
+ */
143
+ exports.handler = async (event, context) => {
144
+ console.log('========================================');
145
+ console.log('Database Migration Lambda Started');
146
+ console.log('========================================');
147
+ console.log('Event:', JSON.stringify(event, null, 2));
148
+ console.log('Context:', JSON.stringify({
149
+ requestId: context.requestId,
150
+ functionName: context.functionName,
151
+ remainingTimeInMillis: context.getRemainingTimeInMillis(),
152
+ }, null, 2));
153
+
154
+ // Extract migration parameters from event
155
+ const { migrationId, dbType, stage } = extractMigrationParams(event);
156
+
157
+ // Check for action parameter (direct invocation for status checks)
158
+ const action = event.action || 'migrate'; // Default to migration
159
+
160
+ // Handle checkStatus action
161
+ if (action === 'checkStatus') {
162
+ console.log(`\n========================================`);
163
+ console.log(`Action: checkStatus (dbType=${dbType}, stage=${stage})`);
164
+ console.log(`========================================`);
165
+
166
+ try {
167
+ const checkDbStateUseCase = new CheckDatabaseStateUseCase({ prismaRunner });
168
+ const status = await checkDbStateUseCase.execute(dbType, stage);
169
+
170
+ console.log('✓ Database state check completed');
171
+ console.log(` Up to date: ${status.upToDate}`);
172
+ console.log(` Pending migrations: ${status.pendingMigrations}`);
173
+
174
+ return {
175
+ statusCode: 200,
176
+ body: status,
177
+ };
178
+ } catch (error) {
179
+ console.error('❌ Database state check failed:', error.message);
180
+ return {
181
+ statusCode: 500,
182
+ body: {
183
+ success: false,
184
+ error: sanitizeError(error.message),
185
+ upToDate: false,
186
+ },
187
+ };
188
+ }
189
+ }
190
+
191
+ // Otherwise, handle migration (existing code)
192
+ console.log(`\n========================================`);
193
+ console.log(`Action: migrate (migrationId=${migrationId || 'new'})`);
194
+ console.log(`========================================`);
195
+
196
+ // Get environment variables
197
+ const databaseUrl = process.env.DATABASE_URL;
198
+
199
+ try {
200
+ // Validate DATABASE_URL is set
201
+ if (!databaseUrl) {
202
+ const error = 'DATABASE_URL environment variable is not set';
203
+ console.error('❌ Validation failed:', error);
204
+ return {
205
+ statusCode: 500,
206
+ body: JSON.stringify({
207
+ success: false,
208
+ error,
209
+ }),
210
+ };
211
+ }
212
+
213
+ console.log('✓ Environment validated');
214
+ console.log(` Database Type: ${dbType}`);
215
+ console.log(` Stage: ${stage}`);
216
+ console.log(` Database URL: ${sanitizeDatabaseUrl(databaseUrl)}`);
217
+
218
+ // Update migration status to RUNNING (if migrationId provided)
219
+ if (migrationId) {
220
+ console.log(`\n✓ Updating migration status to RUNNING: ${migrationId}`);
221
+ await migrationStatusRepository.update({
222
+ migrationId,
223
+ stage,
224
+ state: 'RUNNING',
225
+ progress: 10,
226
+ startedAt: new Date().toISOString(),
227
+ });
228
+ }
229
+
230
+ // Create use case with dependencies (Dependency Injection)
231
+ const runDatabaseMigrationUseCase = new RunDatabaseMigrationUseCase({
232
+ prismaRunner,
233
+ });
234
+
235
+ console.log('\n========================================');
236
+ console.log('Executing Database Migration');
237
+ console.log('========================================');
238
+
239
+ // Execute use case (business logic layer)
240
+ const result = await runDatabaseMigrationUseCase.execute({
241
+ dbType,
242
+ stage,
243
+ verbose: true, // Enable verbose output for Lambda CloudWatch logs
244
+ });
245
+
246
+ console.log('✓ Database migration completed successfully');
247
+ console.log('\n========================================');
248
+ console.log('Migration Summary');
249
+ console.log('========================================');
250
+ console.log(` Status: Success`);
251
+ console.log(` Database: ${result.dbType}`);
252
+ console.log(` Stage: ${result.stage}`);
253
+ console.log(` Command: ${result.command}`);
254
+ console.log('========================================');
255
+
256
+ // Update migration status to COMPLETED (if migrationId provided)
257
+ if (migrationId) {
258
+ console.log(`\n✓ Updating migration status to COMPLETED: ${migrationId}`);
259
+ await migrationStatusRepository.update({
260
+ migrationId,
261
+ stage,
262
+ state: 'COMPLETED',
263
+ progress: 100,
264
+ completedAt: new Date().toISOString(),
265
+ migrationCommand: result.command,
266
+ });
267
+ }
268
+
269
+ // Return success response (adapter layer - HTTP mapping)
270
+ const responseBody = {
271
+ success: true,
272
+ message: result.message,
273
+ dbType: result.dbType,
274
+ stage: result.stage,
275
+ migrationCommand: result.command,
276
+ timestamp: new Date().toISOString(),
277
+ };
278
+
279
+ if (migrationId) {
280
+ responseBody.migrationId = migrationId;
281
+ }
282
+
283
+ return {
284
+ statusCode: 200,
285
+ body: JSON.stringify(responseBody),
286
+ };
287
+
288
+ } catch (error) {
289
+ console.error('\n========================================');
290
+ console.error('Migration Failed');
291
+ console.error('========================================');
292
+ console.error('Error:', error.name, error.message);
293
+
294
+ // Log full stack trace to CloudWatch (only visible to developers)
295
+ if (error.stack) {
296
+ console.error('Stack:', error.stack);
297
+ }
298
+
299
+ // Log context if available (from MigrationError)
300
+ if (error.context) {
301
+ console.error('Context:', JSON.stringify(error.context, null, 2));
302
+ }
303
+
304
+ // Map domain errors to HTTP status codes (adapter layer)
305
+ let statusCode = 500;
306
+ let errorMessage = error.message || 'Unknown error occurred';
307
+
308
+ if (error instanceof ValidationError) {
309
+ statusCode = 400; // Bad Request for validation errors
310
+ } else if (error instanceof MigrationError) {
311
+ statusCode = 500; // Internal Server Error for migration failures
312
+ }
313
+
314
+ // Sanitize error message before returning
315
+ const sanitizedError = sanitizeError(errorMessage);
316
+
317
+ // Update migration status to FAILED (if migrationId provided)
318
+ if (migrationId) {
319
+ try {
320
+ console.log(`\n✓ Updating migration status to FAILED: ${migrationId}`);
321
+ await migrationStatusRepository.update({
322
+ migrationId,
323
+ stage,
324
+ state: 'FAILED',
325
+ progress: 0,
326
+ error: sanitizedError,
327
+ failedAt: new Date().toISOString(),
328
+ });
329
+ } catch (updateError) {
330
+ console.error('Failed to update migration status:', updateError.message);
331
+ // Continue - don't let status update failure block error response
332
+ }
333
+ }
334
+
335
+ const errorBody = {
336
+ success: false,
337
+ error: sanitizedError,
338
+ errorType: error.name || 'Error',
339
+ // Only include stack traces in development environments
340
+ ...(stage === 'dev' || stage === 'local' || stage === 'test' ? { stack: error.stack } : {}),
341
+ };
342
+
343
+ if (migrationId) {
344
+ errorBody.migrationId = migrationId;
345
+ }
346
+
347
+ return {
348
+ statusCode,
349
+ body: JSON.stringify(errorBody),
350
+ };
351
+ }
352
+ };
@@ -0,0 +1,27 @@
1
+ const { createHandler } = require('@friggframework/core');
2
+ const { loadAppDefinition } = require('../app-definition-loader');
3
+ const { createQueueWorker } = require('../backend-utils');
4
+
5
+ const handlers = {};
6
+ const { integrations: integrationClasses } = loadAppDefinition();
7
+
8
+ integrationClasses.forEach((IntegrationClass) => {
9
+ const defaultQueueWorker = createQueueWorker(IntegrationClass);
10
+
11
+ handlers[`${IntegrationClass.Definition.name}`] = {
12
+ queueWorker: createHandler({
13
+ eventName: `Queue Worker for ${IntegrationClass.Definition.name}`,
14
+ isUserFacingResponse: false,
15
+ method: async (event, context) => {
16
+ const worker = new defaultQueueWorker();
17
+ await worker.run(event, context);
18
+ return {
19
+ message: 'Successfully processed the Generic Queue Worker',
20
+ input: event,
21
+ };
22
+ },
23
+ }),
24
+ };
25
+ });
26
+
27
+ module.exports = { handlers };