@friggframework/core 2.0.0--canary.461.3d6d8ad.0 → 2.0.0--canary.461.5100cd8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Lambda Invoker Adapter
3
+ * Infrastructure layer - handles AWS Lambda function invocations
4
+ *
5
+ * Part of Hexagonal Architecture:
6
+ * - Infrastructure Layer adapter for AWS SDK
7
+ * - Used by Domain Layer use cases
8
+ * - Isolates AWS-specific logic from business logic
9
+ */
10
+
11
+ const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
12
+
13
+ /**
14
+ * Custom error for Lambda invocation failures
15
+ * Provides structured error information for debugging
16
+ */
17
+ class LambdaInvocationError extends Error {
18
+ constructor(message, functionName, statusCode) {
19
+ super(message);
20
+ this.name = 'LambdaInvocationError';
21
+ this.functionName = functionName;
22
+ this.statusCode = statusCode;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Adapter for invoking AWS Lambda functions
28
+ *
29
+ * Infrastructure layer - handles AWS SDK communication
30
+ * Converts AWS SDK responses to domain-friendly formats
31
+ */
32
+ class LambdaInvoker {
33
+ /**
34
+ * @param {LambdaClient} lambdaClient - AWS Lambda client (injected for testability)
35
+ */
36
+ constructor(lambdaClient = new LambdaClient({})) {
37
+ this.client = lambdaClient;
38
+ }
39
+
40
+ /**
41
+ * Invoke Lambda function synchronously
42
+ *
43
+ * @param {string} functionName - Lambda function name or ARN
44
+ * @param {Object} payload - Event payload to send to Lambda
45
+ * @returns {Promise<Object>} Parsed response body
46
+ * @throws {LambdaInvocationError} If Lambda returns error status
47
+ * @throws {Error} If AWS SDK call fails
48
+ */
49
+ async invoke(functionName, payload) {
50
+ try {
51
+ const command = new InvokeCommand({
52
+ FunctionName: functionName,
53
+ InvocationType: 'RequestResponse', // Synchronous
54
+ Payload: JSON.stringify(payload),
55
+ });
56
+
57
+ const response = await this.client.send(command);
58
+
59
+ // Parse response payload
60
+ let result;
61
+ try {
62
+ result = JSON.parse(Buffer.from(response.Payload).toString());
63
+ } catch (parseError) {
64
+ throw new LambdaInvocationError(
65
+ `Failed to parse Lambda response: ${parseError.message}`,
66
+ functionName,
67
+ null
68
+ );
69
+ }
70
+
71
+ // Check status code
72
+ if (result.statusCode === 200) {
73
+ return result.body;
74
+ }
75
+
76
+ // Lambda returned error status
77
+ const errorMessage = result.body?.error || 'Lambda invocation failed';
78
+ throw new LambdaInvocationError(
79
+ `Lambda ${functionName} returned error: ${errorMessage}`,
80
+ functionName,
81
+ result.statusCode
82
+ );
83
+ } catch (error) {
84
+ // Re-throw LambdaInvocationError as-is
85
+ if (error instanceof LambdaInvocationError) {
86
+ throw error;
87
+ }
88
+
89
+ // Wrap AWS SDK errors
90
+ throw new Error(`Failed to invoke Lambda ${functionName}: ${error.message}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ module.exports = { LambdaInvoker, LambdaInvocationError };
96
+
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Check Migration Status Use Case
2
+ * Check Database State Use Case
3
3
  *
4
- * Domain logic for checking if database has pending migrations.
5
- * Does NOT trigger migrations, just reports status.
4
+ * Domain logic for checking database state (pending migrations, errors, etc).
5
+ * Does NOT trigger migrations, just reports current state.
6
6
  *
7
7
  * Architecture: Hexagonal/Clean
8
8
  * - Use Case (Domain Layer)
9
9
  * - Depends on prismaRunner (Infrastructure abstraction)
10
- * - Called by Router (Adapter Layer)
10
+ * - Called by Router or other Use Cases (Adapter Layer)
11
11
  */
12
12
 
13
13
  class ValidationError extends Error {
@@ -17,7 +17,7 @@ class ValidationError extends Error {
17
17
  }
18
18
  }
19
19
 
20
- class CheckMigrationStatusUseCase {
20
+ class CheckDatabaseStateUseCase {
21
21
  /**
22
22
  * @param {Object} dependencies
23
23
  * @param {Object} dependencies.prismaRunner - Prisma runner utility
@@ -75,7 +75,7 @@ class CheckMigrationStatusUseCase {
75
75
  }
76
76
 
77
77
  module.exports = {
78
- CheckMigrationStatusUseCase,
78
+ CheckDatabaseStateUseCase,
79
79
  ValidationError,
80
80
  };
81
81
 
@@ -0,0 +1,60 @@
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
+
@@ -28,11 +28,10 @@ const {
28
28
  ValidationError: GetValidationError,
29
29
  NotFoundError,
30
30
  } = require('../../database/use-cases/get-migration-status-use-case');
31
+ const { LambdaInvoker } = require('../../database/adapters/lambda-invoker');
31
32
  const {
32
- CheckMigrationStatusUseCase,
33
- ValidationError: CheckValidationError,
34
- } = require('../../database/use-cases/check-migration-status-use-case');
35
- const prismaRunner = require('../../database/utils/prisma-runner');
33
+ GetDatabaseStateViaWorkerUseCase,
34
+ } = require('../../database/use-cases/get-database-state-via-worker-use-case');
36
35
 
37
36
  const router = Router();
38
37
 
@@ -46,7 +45,16 @@ const triggerMigrationUseCase = new TriggerDatabaseMigrationUseCase({
46
45
  // Note: QueuerUtil is used directly in the use case (static utility)
47
46
  });
48
47
  const getStatusUseCase = new GetMigrationStatusUseCase({ migrationStatusRepository });
49
- const checkMigrationStatusUseCase = new CheckMigrationStatusUseCase({ prismaRunner });
48
+
49
+ // Lambda invocation for database state check (keeps router lightweight)
50
+ const lambdaInvoker = new LambdaInvoker();
51
+ const workerFunctionName = process.env.WORKER_FUNCTION_NAME ||
52
+ `${process.env.SERVICE || 'unknown'}-${process.env.STAGE || 'production'}-dbMigrationWorker`;
53
+
54
+ const getDatabaseStateUseCase = new GetDatabaseStateViaWorkerUseCase({
55
+ lambdaInvoker,
56
+ workerFunctionName,
57
+ });
50
58
 
51
59
  /**
52
60
  * Admin API key validation middleware
@@ -147,25 +155,24 @@ router.get(
147
155
  '/db-migrate/status',
148
156
  catchAsyncError(async (req, res) => {
149
157
  const stage = req.query.stage || process.env.STAGE || 'production';
150
- const dbType = process.env.DB_TYPE || 'postgresql'; // Hardcoded for PostgreSQL migrations
151
158
 
152
- console.log(`Checking migration status: dbType=${dbType}, stage=${stage}`);
159
+ console.log(`Checking database state: stage=${stage}, worker=${workerFunctionName}`);
153
160
 
154
161
  try {
155
- const status = await checkMigrationStatusUseCase.execute(dbType, stage);
162
+ // Invoke worker Lambda to check database state
163
+ const status = await getDatabaseStateUseCase.execute(stage);
156
164
 
157
165
  res.status(200).json(status);
158
166
  } catch (error) {
159
- // Handle validation errors (400 Bad Request)
160
- if (error instanceof CheckValidationError) {
161
- return res.status(400).json({
162
- success: false,
163
- error: error.message,
164
- });
165
- }
167
+ // Log full error for debugging
168
+ console.error('Database state check failed:', error);
166
169
 
167
- // Re-throw other errors for global error handler
168
- throw error;
170
+ // Return sanitized error to client
171
+ return res.status(500).json({
172
+ success: false,
173
+ error: 'Failed to check database state',
174
+ details: error.message,
175
+ });
169
176
  }
170
177
  })
171
178
  );
@@ -46,6 +46,9 @@ const {
46
46
  MigrationError,
47
47
  ValidationError,
48
48
  } = require('../../database/use-cases/run-database-migration-use-case');
49
+ const {
50
+ CheckDatabaseStateUseCase,
51
+ } = require('../../database/use-cases/check-database-state-use-case');
49
52
  const {
50
53
  MigrationStatusRepositoryS3,
51
54
  } = require('../../database/repositories/migration-status-repository-s3');
@@ -151,6 +154,45 @@ exports.handler = async (event, context) => {
151
154
  // Extract migration parameters from event
152
155
  const { migrationId, dbType, stage } = extractMigrationParams(event);
153
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
+
154
196
  // Get environment variables
155
197
  const databaseUrl = process.env.DATABASE_URL;
156
198
 
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.461.3d6d8ad.0",
4
+ "version": "2.0.0--canary.461.5100cd8.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
8
+ "@aws-sdk/client-lambda": "^3.714.0",
8
9
  "@aws-sdk/client-sqs": "^3.588.0",
9
10
  "@hapi/boom": "^10.0.1",
10
11
  "bcryptjs": "^2.4.3",
@@ -37,9 +38,9 @@
37
38
  }
38
39
  },
39
40
  "devDependencies": {
40
- "@friggframework/eslint-config": "2.0.0--canary.461.3d6d8ad.0",
41
- "@friggframework/prettier-config": "2.0.0--canary.461.3d6d8ad.0",
42
- "@friggframework/test": "2.0.0--canary.461.3d6d8ad.0",
41
+ "@friggframework/eslint-config": "2.0.0--canary.461.5100cd8.0",
42
+ "@friggframework/prettier-config": "2.0.0--canary.461.5100cd8.0",
43
+ "@friggframework/test": "2.0.0--canary.461.5100cd8.0",
43
44
  "@prisma/client": "^6.17.0",
44
45
  "@types/lodash": "4.17.15",
45
46
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -79,5 +80,5 @@
79
80
  "publishConfig": {
80
81
  "access": "public"
81
82
  },
82
- "gitHead": "3d6d8ad065c2530fee34dcda5ee304979c718a65"
83
+ "gitHead": "5100cd81ceb3fe903e128b5f9530e09cb3e89495"
83
84
  }