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

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 +88 -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 +37 -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,108 @@
1
+ const { prisma } = require('../prisma');
2
+ const { mongoose } = require('../mongoose');
3
+ const {
4
+ HealthCheckRepositoryInterface,
5
+ } = require('./health-check-repository-interface');
6
+
7
+ /**
8
+ * Repository for Health Check database operations.
9
+ * Provides atomic database operations for health testing.
10
+ *
11
+ * Follows DDD/Hexagonal Architecture:
12
+ * - Infrastructure Layer (this repository)
13
+ * - Pure database operations only, no business logic
14
+ * - Used by Application Layer (Use Cases)
15
+ *
16
+ * Works identically for both MongoDB and PostgreSQL:
17
+ * - Uses Prisma for database operations
18
+ * - Encryption happens transparently via Prisma extension
19
+ * - Both MongoDB and PostgreSQL use same Prisma API
20
+ *
21
+ * Migration from Mongoose to Prisma:
22
+ * - Replaced Mongoose models with Prisma client
23
+ * - Uses Credential model for encryption testing
24
+ * - Maintains same method signatures for compatibility
25
+ */
26
+ class HealthCheckRepository extends HealthCheckRepositoryInterface {
27
+ constructor() {
28
+ super();
29
+ }
30
+
31
+ /**
32
+ * Get database connection state
33
+ * @returns {Object} Object with readyState, stateName, and isConnected
34
+ */
35
+ getDatabaseConnectionState() {
36
+ const stateMap = {
37
+ 0: 'disconnected',
38
+ 1: 'connected',
39
+ 2: 'connecting',
40
+ 3: 'disconnecting',
41
+ };
42
+ const readyState = mongoose.connection.readyState;
43
+
44
+ return {
45
+ readyState,
46
+ stateName: stateMap[readyState],
47
+ isConnected: readyState === 1,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Ping the database to verify connectivity
53
+ * @param {number} maxTimeMS - Maximum time to wait for ping response
54
+ * @returns {Promise<number>} Response time in milliseconds
55
+ * @throws {Error} If database is not connected or ping fails
56
+ */
57
+ async pingDatabase(maxTimeMS = 2000) {
58
+ const pingStart = Date.now();
59
+ await mongoose.connection.db.admin().ping({ maxTimeMS });
60
+ return Date.now() - pingStart;
61
+ }
62
+
63
+ /**
64
+ * Create a test credential for encryption testing
65
+ * @param {Object} credentialData - Credential data to create
66
+ * @returns {Promise<Object>} Created credential
67
+ */
68
+ async createCredential(credentialData) {
69
+ return await prisma.credential.create({
70
+ data: credentialData,
71
+ });
72
+ }
73
+
74
+ /**
75
+ * Find a credential by ID
76
+ * @param {string} id - Credential ID
77
+ * @returns {Promise<Object|null>} Found credential or null
78
+ */
79
+ async findCredentialById(id) {
80
+ return await prisma.credential.findUnique({
81
+ where: { id },
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Get raw credential from database bypassing Prisma encryption extension
87
+ * @param {string} id - Credential ID
88
+ * @returns {Promise<Object|null>} Raw credential from database
89
+ */
90
+ async getRawCredentialById(id) {
91
+ return await mongoose.connection.db
92
+ .collection('credentials')
93
+ .findOne({ _id: id });
94
+ }
95
+
96
+ /**
97
+ * Delete a credential by ID
98
+ * @param {string} id - Credential ID
99
+ * @returns {Promise<void>}
100
+ */
101
+ async deleteCredential(id) {
102
+ await prisma.credential.delete({
103
+ where: { id },
104
+ });
105
+ }
106
+ }
107
+
108
+ module.exports = { HealthCheckRepository };
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Migration Status Repository - S3 Storage
3
+ *
4
+ * Infrastructure Layer - Hexagonal Architecture
5
+ *
6
+ * Stores migration status in S3 to avoid chicken-and-egg dependency on User/Process tables.
7
+ * Initial database migrations can't use Process table (requires User FK which doesn't exist yet).
8
+ */
9
+
10
+ const { S3Client, PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
11
+ const { randomUUID } = require('crypto');
12
+
13
+ class MigrationStatusRepositoryS3 {
14
+ /**
15
+ * @param {string} bucketName - S3 bucket name for migration status storage
16
+ * @param {S3Client} s3Client - Optional S3 client (for testing)
17
+ */
18
+ constructor(bucketName, s3Client = null) {
19
+ this.bucketName = bucketName;
20
+ this.s3Client = s3Client || new S3Client({ region: process.env.AWS_REGION || 'us-east-1' });
21
+ }
22
+
23
+ /**
24
+ * Build S3 key for migration status
25
+ * @param {string} migrationId - Migration identifier
26
+ * @param {string} stage - Deployment stage
27
+ * @returns {string} S3 key
28
+ */
29
+ _buildS3Key(migrationId, stage) {
30
+ return `migrations/${stage}/${migrationId}.json`;
31
+ }
32
+
33
+ /**
34
+ * Create new migration status record
35
+ * @param {Object} data - Migration data
36
+ * @param {string} [data.migrationId] - Migration ID (generates UUID if not provided)
37
+ * @param {string} data.stage - Deployment stage
38
+ * @param {string} [data.triggeredBy] - User or system that triggered migration
39
+ * @param {string} [data.triggeredAt] - ISO timestamp
40
+ * @returns {Promise<Object>} Created migration status
41
+ */
42
+ async create(data) {
43
+ const migrationId = data.migrationId || randomUUID();
44
+ const timestamp = data.triggeredAt || new Date().toISOString();
45
+
46
+ const status = {
47
+ migrationId,
48
+ stage: data.stage,
49
+ state: 'INITIALIZING',
50
+ progress: 0,
51
+ triggeredBy: data.triggeredBy || 'system',
52
+ triggeredAt: timestamp,
53
+ createdAt: timestamp,
54
+ updatedAt: timestamp,
55
+ };
56
+
57
+ const key = this._buildS3Key(migrationId, data.stage);
58
+
59
+ await this.s3Client.send(
60
+ new PutObjectCommand({
61
+ Bucket: this.bucketName,
62
+ Key: key,
63
+ Body: JSON.stringify(status, null, 2),
64
+ ContentType: 'application/json',
65
+ })
66
+ );
67
+
68
+ return status;
69
+ }
70
+
71
+ /**
72
+ * Update existing migration status
73
+ * @param {Object} data - Update data
74
+ * @param {string} data.migrationId - Migration ID
75
+ * @param {string} data.stage - Deployment stage
76
+ * @param {string} [data.state] - New state
77
+ * @param {number} [data.progress] - Progress percentage (0-100)
78
+ * @param {string} [data.error] - Error message if failed
79
+ * @param {string} [data.completedAt] - Completion timestamp
80
+ * @returns {Promise<Object>} Updated migration status
81
+ */
82
+ async update(data) {
83
+ const key = this._buildS3Key(data.migrationId, data.stage);
84
+
85
+ // Get existing status
86
+ const existing = await this.get(data.migrationId, data.stage);
87
+
88
+ // Merge updates
89
+ const updated = {
90
+ ...existing,
91
+ ...data,
92
+ updatedAt: new Date().toISOString(),
93
+ };
94
+
95
+ await this.s3Client.send(
96
+ new PutObjectCommand({
97
+ Bucket: this.bucketName,
98
+ Key: key,
99
+ Body: JSON.stringify(updated, null, 2),
100
+ ContentType: 'application/json',
101
+ })
102
+ );
103
+
104
+ return updated;
105
+ }
106
+
107
+ /**
108
+ * Get migration status by ID
109
+ * @param {string} migrationId - Migration ID
110
+ * @param {string} stage - Deployment stage
111
+ * @returns {Promise<Object>} Migration status
112
+ * @throws {Error} If migration not found
113
+ */
114
+ async get(migrationId, stage) {
115
+ const key = this._buildS3Key(migrationId, stage);
116
+
117
+ try {
118
+ const response = await this.s3Client.send(
119
+ new GetObjectCommand({
120
+ Bucket: this.bucketName,
121
+ Key: key,
122
+ })
123
+ );
124
+
125
+ const body = await response.Body.transformToString();
126
+ return JSON.parse(body);
127
+ } catch (error) {
128
+ if (error.name === 'NoSuchKey') {
129
+ throw new Error(`Migration not found: ${migrationId}`);
130
+ }
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+
136
+ module.exports = { MigrationStatusRepositoryS3 };
137
+
@@ -0,0 +1,29 @@
1
+ class CheckDatabaseHealthUseCase {
2
+ /**
3
+ * @param {Object} params
4
+ * @param {import('../repositories/health-check-repository-interface').HealthCheckRepositoryInterface} params.healthCheckRepository
5
+ */
6
+ constructor({ healthCheckRepository }) {
7
+ this.repository = healthCheckRepository;
8
+ }
9
+
10
+ /**
11
+ * @returns {Promise<{status: string, state: string, responseTime?: number}>}
12
+ */
13
+ async execute() {
14
+ const { stateName, isConnected } = await this.repository.getDatabaseConnectionState();
15
+
16
+ const result = {
17
+ status: isConnected ? 'healthy' : 'unhealthy',
18
+ state: stateName,
19
+ };
20
+
21
+ if (isConnected) {
22
+ result.responseTime = await this.repository.pingDatabase(2000);
23
+ }
24
+
25
+ return result;
26
+ }
27
+ }
28
+
29
+ module.exports = { CheckDatabaseHealthUseCase };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Check Database State Use Case
3
+ *
4
+ * Domain logic for checking database state (pending migrations, errors, etc).
5
+ * Does NOT trigger migrations, just reports current state.
6
+ *
7
+ * Architecture: Hexagonal/Clean
8
+ * - Use Case (Domain Layer)
9
+ * - Depends on prismaRunner (Infrastructure abstraction)
10
+ * - Called by Router or other Use Cases (Adapter Layer)
11
+ */
12
+
13
+ class ValidationError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = 'ValidationError';
17
+ }
18
+ }
19
+
20
+ class CheckDatabaseStateUseCase {
21
+ /**
22
+ * @param {Object} dependencies
23
+ * @param {Object} dependencies.prismaRunner - Prisma runner utility
24
+ */
25
+ constructor({ prismaRunner }) {
26
+ if (!prismaRunner) {
27
+ throw new Error('prismaRunner dependency is required');
28
+ }
29
+ this.prismaRunner = prismaRunner;
30
+ }
31
+
32
+ /**
33
+ * Execute check migration status
34
+ *
35
+ * @param {string} dbType - Database type (postgresql or mongodb)
36
+ * @param {string} stage - Deployment stage (default: 'production')
37
+ * @returns {Promise<Object>} Migration status
38
+ */
39
+ async execute(dbType, stage = 'production') {
40
+ // Validate inputs
41
+ if (!dbType) {
42
+ throw new ValidationError('dbType is required');
43
+ }
44
+
45
+ if (!['postgresql', 'mongodb'].includes(dbType)) {
46
+ throw new ValidationError('dbType must be postgresql or mongodb');
47
+ }
48
+
49
+ console.log(`Checking migration status for ${dbType} in ${stage}`);
50
+
51
+ // Check database state using Prisma
52
+ const state = await this.prismaRunner.checkDatabaseState(dbType);
53
+
54
+ // Build response
55
+ const response = {
56
+ upToDate: state.upToDate,
57
+ pendingMigrations: state.pendingMigrations || 0,
58
+ dbType,
59
+ stage,
60
+ };
61
+
62
+ // Add error if present
63
+ if (state.error) {
64
+ response.error = state.error;
65
+ response.recommendation = 'Run POST /db-migrate to initialize database';
66
+ }
67
+
68
+ // Add recommendation if migrations pending
69
+ if (!state.upToDate && state.pendingMigrations > 0) {
70
+ response.recommendation = `Run POST /db-migrate to apply ${state.pendingMigrations} pending migration(s)`;
71
+ }
72
+
73
+ return response;
74
+ }
75
+ }
76
+
77
+ module.exports = {
78
+ CheckDatabaseStateUseCase,
79
+ ValidationError,
80
+ };
81
+
@@ -0,0 +1,83 @@
1
+ class CheckEncryptionHealthUseCase {
2
+ constructor({ testEncryptionUseCase }) {
3
+ this.testEncryptionUseCase = testEncryptionUseCase;
4
+ }
5
+
6
+ async execute() {
7
+ const config = this._getEncryptionConfiguration();
8
+
9
+ if (config.isBypassed || config.mode === 'none') {
10
+ const testResult = config.isBypassed
11
+ ? 'Encryption bypassed for this stage'
12
+ : 'No encryption keys configured';
13
+
14
+ return {
15
+ status: 'disabled',
16
+ mode: config.mode,
17
+ bypassed: config.isBypassed,
18
+ stage: config.stage,
19
+ testResult,
20
+ encryptionWorks: false,
21
+ debug: {
22
+ hasKMS: config.hasKMS,
23
+ hasAES: config.hasAES,
24
+ },
25
+ };
26
+ }
27
+
28
+ try {
29
+ const testResults = await this.testEncryptionUseCase.execute();
30
+
31
+ return {
32
+ ...testResults,
33
+ mode: config.mode,
34
+ bypassed: config.isBypassed,
35
+ stage: config.stage,
36
+ debug: {
37
+ hasKMS: config.hasKMS,
38
+ hasAES: config.hasAES,
39
+ },
40
+ };
41
+ } catch (error) {
42
+ return {
43
+ status: 'unhealthy',
44
+ mode: config.mode,
45
+ bypassed: config.isBypassed,
46
+ stage: config.stage,
47
+ testResult: `Encryption test failed: ${error.message}`,
48
+ encryptionWorks: false,
49
+ debug: {
50
+ hasKMS: config.hasKMS,
51
+ hasAES: config.hasAES,
52
+ },
53
+ };
54
+ }
55
+ }
56
+
57
+ _getEncryptionConfiguration() {
58
+ const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
59
+ process.env;
60
+
61
+ const defaultBypassStages = ['dev', 'test', 'local'];
62
+ const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
63
+ const bypassStages = useEnv
64
+ ? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
65
+ : defaultBypassStages;
66
+
67
+ const isBypassed = bypassStages.includes(STAGE);
68
+ const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
69
+ const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '';
70
+ // Prefer KMS over AES when both are configured (KMS is more secure)
71
+ const mode = hasKMS ? 'kms' : hasAES ? 'aes' : 'none';
72
+
73
+ return {
74
+ stage: STAGE || null,
75
+ isBypassed,
76
+ hasAES,
77
+ hasKMS,
78
+ mode,
79
+ };
80
+ }
81
+ }
82
+
83
+ module.exports = { CheckEncryptionHealthUseCase };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Get Database State Via Worker Use Case
3
+ *
4
+ * Domain logic for getting database state by invoking the worker Lambda.
5
+ * This use case delegates to the worker Lambda which has Prisma CLI installed,
6
+ * keeping the router Lambda lightweight.
7
+ *
8
+ * Architecture: Hexagonal/Clean
9
+ * - Use Case (Domain Layer)
10
+ * - Depends on LambdaInvoker (Infrastructure abstraction)
11
+ * - Called by Router (Adapter Layer)
12
+ */
13
+
14
+ /**
15
+ * Domain Use Case: Get database state by invoking worker Lambda
16
+ *
17
+ * This use case delegates database state checking to the worker Lambda,
18
+ * which has Prisma CLI installed. Keeps the router Lambda lightweight.
19
+ */
20
+ class GetDatabaseStateViaWorkerUseCase {
21
+ /**
22
+ * @param {Object} dependencies
23
+ * @param {LambdaInvoker} dependencies.lambdaInvoker - Lambda invocation adapter
24
+ * @param {string} dependencies.workerFunctionName - Worker Lambda function name
25
+ */
26
+ constructor({ lambdaInvoker, workerFunctionName }) {
27
+ if (!lambdaInvoker) {
28
+ throw new Error('lambdaInvoker dependency is required');
29
+ }
30
+ if (!workerFunctionName) {
31
+ throw new Error('workerFunctionName is required');
32
+ }
33
+ this.lambdaInvoker = lambdaInvoker;
34
+ this.workerFunctionName = workerFunctionName;
35
+ }
36
+
37
+ /**
38
+ * Execute database state check via worker Lambda
39
+ *
40
+ * @param {string} stage - Deployment stage (prod, dev, etc)
41
+ * @returns {Promise<Object>} Database state result
42
+ */
43
+ async execute(stage = 'production') {
44
+ const dbType = process.env.DB_TYPE || 'postgresql';
45
+
46
+ console.log(`Invoking worker Lambda to check database state: ${this.workerFunctionName}`);
47
+
48
+ // Invoke worker Lambda with checkStatus action
49
+ const result = await this.lambdaInvoker.invoke(this.workerFunctionName, {
50
+ action: 'checkStatus',
51
+ dbType,
52
+ stage,
53
+ });
54
+
55
+ return result;
56
+ }
57
+ }
58
+
59
+ module.exports = { GetDatabaseStateViaWorkerUseCase };
60
+
61
+
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Get Migration Status Use Case
3
+ *
4
+ * Retrieves the status of a database migration by process ID.
5
+ * Formats the Process record for migration-specific response.
6
+ *
7
+ * This use case follows the Frigg hexagonal architecture pattern where:
8
+ * - Routers (adapters) call use cases
9
+ * - Use cases contain business logic and formatting
10
+ * - Use cases call repositories for data access
11
+ */
12
+
13
+ class GetMigrationStatusUseCase {
14
+ /**
15
+ * @param {Object} dependencies
16
+ * @param {Object} dependencies.migrationStatusRepository - Repository for migration status (S3)
17
+ */
18
+ constructor({ migrationStatusRepository }) {
19
+ if (!migrationStatusRepository) {
20
+ throw new Error('migrationStatusRepository dependency is required');
21
+ }
22
+ this.migrationStatusRepository = migrationStatusRepository;
23
+ }
24
+
25
+ /**
26
+ * Execute get migration status
27
+ *
28
+ * @param {string} migrationId - Migration ID to retrieve
29
+ * @param {string} [stage] - Deployment stage (defaults to env.STAGE)
30
+ * @returns {Promise<Object>} Migration status from S3
31
+ * @throws {NotFoundError} If migration not found
32
+ * @throws {ValidationError} If migrationId is invalid
33
+ */
34
+ async execute(migrationId, stage = null) {
35
+ // Validation
36
+ this._validateParams(migrationId);
37
+
38
+ const effectiveStage = stage || process.env.STAGE || 'production';
39
+
40
+ // Get migration status from S3
41
+ try {
42
+ const migrationStatus = await this.migrationStatusRepository.get(migrationId, effectiveStage);
43
+ return migrationStatus;
44
+ } catch (error) {
45
+ if (error.message.includes('not found')) {
46
+ throw new NotFoundError(`Migration not found: ${migrationId}`);
47
+ }
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Validate parameters
54
+ * @private
55
+ */
56
+ _validateParams(migrationId) {
57
+ if (!migrationId) {
58
+ throw new ValidationError('migrationId is required');
59
+ }
60
+
61
+ if (typeof migrationId !== 'string') {
62
+ throw new ValidationError('migrationId must be a string');
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Custom error for validation failures
69
+ */
70
+ class ValidationError extends Error {
71
+ constructor(message) {
72
+ super(message);
73
+ this.name = 'ValidationError';
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Custom error for not found resources
79
+ */
80
+ class NotFoundError extends Error {
81
+ constructor(message) {
82
+ super(message);
83
+ this.name = 'NotFoundError';
84
+ this.statusCode = 404;
85
+ }
86
+ }
87
+
88
+ module.exports = {
89
+ GetMigrationStatusUseCase,
90
+ ValidationError,
91
+ NotFoundError,
92
+ };
93
+