@friggframework/core 2.0.0-next.55 → 2.0.0-next.57

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.
@@ -106,7 +106,7 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
106
106
  const updateDocument = {
107
107
  userId: existing.userId,
108
108
  externalId: existing.externalId,
109
- authIsValid: authIsValid,
109
+ authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
110
110
  data: mergedData,
111
111
  updatedAt: now,
112
112
  };
@@ -143,7 +143,7 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
143
143
  }
144
144
 
145
145
  const plainDocument = {
146
- userId: identifiers.userId,
146
+ userId: toObjectId(identifiers.userId),
147
147
  externalId: identifiers.externalId,
148
148
  authIsValid: details.authIsValid,
149
149
  data: { ...oauthData },
@@ -245,7 +245,7 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
245
245
  if (idObj) filter._id = idObj;
246
246
  }
247
247
  if (identifiers.userId) {
248
- filter.userId = identifiers.userId;
248
+ filter.userId = toObjectId(identifiers.userId);
249
249
  }
250
250
  if (identifiers.externalId !== undefined) {
251
251
  filter.externalId = identifiers.externalId;
@@ -121,7 +121,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
121
121
  data: {
122
122
  userId: existing.userId,
123
123
  externalId: existing.externalId,
124
- authIsValid: authIsValid,
124
+ authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
125
125
  data: mergedData,
126
126
  },
127
127
  });
@@ -111,7 +111,8 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
111
111
  if (!identifiers)
112
112
  throw new Error('identifiers required to upsert credential');
113
113
 
114
- if (!identifiers.userId) {
114
+ // Support both userId (preferred) and user (legacy) for backward compatibility
115
+ if (!identifiers.userId && !identifiers.user) {
115
116
  throw new Error('userId required in identifiers');
116
117
  }
117
118
  if (!identifiers.externalId) {
@@ -138,7 +139,7 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
138
139
  data: {
139
140
  userId: this._convertId(existing.userId),
140
141
  externalId: existing.externalId,
141
- authIsValid: authIsValid,
142
+ authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
142
143
  data: mergedData,
143
144
  },
144
145
  });
@@ -154,7 +155,8 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
154
155
 
155
156
  const created = await this.prisma.credential.create({
156
157
  data: {
157
- userId: this._convertId(identifiers.userId),
158
+ // Use userId from where clause (supports both userId and user fields)
159
+ userId: where.userId,
158
160
  externalId,
159
161
  authIsValid: authIsValid,
160
162
  data: oauthData,
@@ -257,8 +259,11 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
257
259
  const where = {};
258
260
 
259
261
  if (identifiers.id) where.id = this._convertId(identifiers.id);
262
+ // Support both userId (preferred) and user (legacy) for backward compatibility
260
263
  if (identifiers.userId)
261
264
  where.userId = this._convertId(identifiers.userId);
265
+ else if (identifiers.user)
266
+ where.userId = this._convertId(identifiers.user);
262
267
  if (identifiers.externalId) where.externalId = identifiers.externalId;
263
268
 
264
269
  return where;
@@ -122,34 +122,140 @@ Or simply don't configure any encryption keys. In Production field level encrypt
122
122
 
123
123
  ## Encrypted Fields
124
124
 
125
- Fields are defined in `encryption-schema-registry.js`:
125
+ Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
126
126
 
127
+ **Core fields include**:
128
+ - OAuth tokens: `access_token`, `refresh_token`, `id_token`
129
+ - API keys: `api_key`, `apiKey`, `API_KEY_VALUE`
130
+ - Basic auth: `password`
131
+ - OAuth client credentials: `client_secret`
132
+
133
+ **Note**: API modules should use `api_key` (snake_case) in their `apiPropertiesToPersist.credential` arrays for consistency with OAuth2Requester and BasicAuthRequester conventions.
134
+
135
+ ### API Module Credential Naming Conventions
136
+
137
+ When creating API module definitions, use **snake_case** for credential property names to ensure automatic encryption:
138
+
139
+ **✅ Recommended (automatically encrypted):**
127
140
  ```javascript
128
- const ENCRYPTION_SCHEMA = {
129
- Credential: {
130
- fields: [
131
- 'data.access_token', // OAuth access token
132
- 'data.refresh_token', // OAuth refresh token
133
- 'data.id_token', // OpenID Connect ID token
134
- ],
135
- },
136
- IntegrationMapping: {
137
- fields: ['mapping'], // Complete mapping object
138
- },
139
- User: {
140
- fields: ['hashword'], // Password hash
141
+ // API Module Definition
142
+ const Definition = {
143
+ requiredAuthMethods: {
144
+ apiPropertiesToPersist: {
145
+ // For API key authentication
146
+ credential: ['api_key'], // Automatically encrypted
147
+ // or for OAuth authentication
148
+ credential: ['access_token', 'refresh_token'], // ✅ OAuth - encrypted
149
+ // or for Basic authentication
150
+ credential: ['username', 'password'], // Basic auth - encrypted
151
+ }
152
+ }
153
+ };
154
+
155
+ // API class (extends ApiKeyRequester)
156
+ class MyApi extends ApiKeyRequester {
157
+ constructor(params) {
158
+ super(params);
159
+ this.api_key = params.api_key; // ✅ snake_case convention
160
+ }
161
+ }
162
+ ```
163
+
164
+ **❌ Avoid (requires manual encryption schema):**
165
+ ```javascript
166
+ apiPropertiesToPersist: {
167
+ credential: ['customToken', 'proprietaryKey'] // ❌ Not in core schema
168
+ }
169
+ ```
170
+
171
+ For custom credential fields not in the core schema, use the custom encryption schema feature (see below).
172
+
173
+ ### Extending Encryption Schema
174
+
175
+ #### Option 1: Module-Level Encryption (API Module Developers)
176
+
177
+ **NEW**: API modules can now declare their encryption requirements directly in the module definition:
178
+
179
+ ```javascript
180
+ // api-module-library/my-service/definition.js
181
+ const Definition = {
182
+ moduleName: 'myService',
183
+ API: MyServiceApi,
184
+
185
+ // Declare which credential fields need encryption
186
+ encryption: {
187
+ credentialFields: ['api_key', 'webhook_secret']
141
188
  },
142
- Token: {
143
- fields: ['token'], // Authentication token
189
+
190
+ requiredAuthMethods: {
191
+ apiPropertiesToPersist: {
192
+ credential: ['api_key', 'webhook_secret'], // These will be auto-encrypted
193
+ entity: []
194
+ },
195
+ // ... other methods
196
+ }
197
+ };
198
+ ```
199
+
200
+ **How it works**:
201
+ 1. Module declares `encryption.credentialFields` array
202
+ 2. Framework automatically adds `data.` prefix: `['api_key']` → `['data.api_key']`
203
+ 3. Fields are merged with core encryption schema on app startup
204
+ 4. All modules across all integrations are scanned and combined
205
+
206
+ **Benefits**:
207
+ - ✅ Module authors control their own security requirements
208
+ - ✅ No need to modify core framework or app configuration
209
+ - ✅ Automatic encryption for API key-based integrations
210
+ - ✅ Works seamlessly with `apiPropertiesToPersist`
211
+
212
+ **Example - API Key Module**:
213
+ ```javascript
214
+ // API Module Definition
215
+ const Definition = {
216
+ moduleName: 'axiscare',
217
+ API: AxisCareApi,
218
+ encryption: {
219
+ credentialFields: ['api_key'] // Auto-encrypted as 'data.api_key'
144
220
  },
221
+ requiredAuthMethods: {
222
+ apiPropertiesToPersist: {
223
+ credential: ['api_key'] // Will be encrypted automatically
224
+ }
225
+ }
145
226
  };
227
+
228
+ // API Class (extends ApiKeyRequester)
229
+ class AxisCareApi extends ApiKeyRequester {
230
+ constructor(params) {
231
+ super(params);
232
+ this.api_key = params.api_key; // snake_case convention
233
+ }
234
+ }
146
235
  ```
147
236
 
148
- ### Extending Encryption Schema
237
+ **Example - Custom Authentication**:
238
+ ```javascript
239
+ const Definition = {
240
+ moduleName: 'customService',
241
+ encryption: {
242
+ credentialFields: [
243
+ 'signing_key',
244
+ 'webhook_secret',
245
+ 'data.custom_nested_field' // Can specify data. prefix explicitly
246
+ ]
247
+ }
248
+ };
249
+ ```
250
+
251
+ **Limitations**:
252
+ - Only supports Credential model fields (stored in `credential.data`)
253
+ - Cannot encrypt entity fields or custom models (use app-level schema for those)
254
+ - Applied globally once - module schemas loaded at app startup
149
255
 
150
- #### Recommended: Custom Schema via appDefinition (Integration Developers)
256
+ #### Option 2: App-Level Custom Schema (Integration Developers)
151
257
 
152
- Integration developers can extend encryption without modifying core framework files:
258
+ Integration developers can extend encryption without modifying core framework files.
153
259
 
154
260
  **In `backend/index.js`:**
155
261
 
@@ -234,7 +340,7 @@ await prisma.asanaTaskMapping.create({
234
340
  FRIGG_DEBUG=1 npm run frigg:start
235
341
  ```
236
342
 
237
- #### Advanced: Modifying Core Schema (Framework Developers)
343
+ #### Option 3: Modifying Core Schema (Framework Developers)
238
344
 
239
345
  Framework developers maintaining core models can modify `encryption-schema-registry.js`:
240
346
 
@@ -19,6 +19,11 @@ const CORE_ENCRYPTION_SCHEMA = {
19
19
  'data.access_token',
20
20
  'data.refresh_token',
21
21
  'data.id_token',
22
+ 'data.api_key',
23
+ 'data.apiKey',
24
+ 'data.API_KEY_VALUE',
25
+ 'data.password',
26
+ 'data.client_secret',
22
27
  ],
23
28
  },
24
29
 
@@ -109,6 +114,74 @@ function registerCustomSchema(schema) {
109
114
  );
110
115
  }
111
116
 
117
+ /**
118
+ * Extracts credential field paths from module definitions
119
+ * @param {Array} moduleDefinitions - Array of module definition objects
120
+ * @returns {Array<string>} Array of field paths with data. prefix
121
+ */
122
+ function extractCredentialFieldsFromModules(moduleDefinitions) {
123
+ const fields = [];
124
+
125
+ for (const moduleDef of moduleDefinitions) {
126
+ if (!moduleDef?.encryption?.credentialFields) {
127
+ continue;
128
+ }
129
+
130
+ const credentialFields = moduleDef.encryption.credentialFields;
131
+ if (!Array.isArray(credentialFields) || credentialFields.length === 0) {
132
+ continue;
133
+ }
134
+
135
+ for (const field of credentialFields) {
136
+ const prefixedField = field.startsWith('data.') ? field : `data.${field}`;
137
+ fields.push(prefixedField);
138
+ }
139
+ }
140
+
141
+ return [...new Set(fields)];
142
+ }
143
+
144
+ /**
145
+ * Loads and registers encryption schemas from API module definitions.
146
+ * Each module can declare credentialFields to encrypt in its encryption config.
147
+ *
148
+ * @param {Array} integrations - Array of integration classes with modules
149
+ */
150
+ function loadModuleEncryptionSchemas(integrations) {
151
+ if (!integrations) {
152
+ throw new Error('integrations parameter is required');
153
+ }
154
+
155
+ if (!Array.isArray(integrations)) {
156
+ throw new Error('integrations must be an array');
157
+ }
158
+
159
+ if (integrations.length === 0) {
160
+ return;
161
+ }
162
+
163
+ const { getModulesDefinitionFromIntegrationClasses } = require('../integrations/utils/map-integration-dto');
164
+
165
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(integrations);
166
+ const credentialFields = extractCredentialFieldsFromModules(moduleDefinitions);
167
+
168
+ if (credentialFields.length === 0) {
169
+ return;
170
+ }
171
+
172
+ const moduleSchema = {
173
+ Credential: {
174
+ fields: credentialFields
175
+ }
176
+ };
177
+
178
+ logger.info(
179
+ `Registering module-level encryption for ${credentialFields.length} credential fields`
180
+ );
181
+
182
+ registerCustomSchema(moduleSchema);
183
+ }
184
+
112
185
  /**
113
186
  * Loads and registers custom encryption schema from appDefinition.
114
187
  * Gracefully handles cases where appDefinition is not available.
@@ -139,11 +212,17 @@ function loadCustomEncryptionSchema() {
139
212
  return; // No app definition found
140
213
  }
141
214
 
215
+ // Load app-level custom schema
142
216
  const customSchema = appDefinition.encryption?.schema;
143
-
144
217
  if (customSchema && Object.keys(customSchema).length > 0) {
145
218
  registerCustomSchema(customSchema);
146
219
  }
220
+
221
+ // Load module-level encryption schemas from integrations
222
+ const integrations = appDefinition.integrations;
223
+ if (integrations && Array.isArray(integrations)) {
224
+ loadModuleEncryptionSchemas(integrations);
225
+ }
147
226
  } catch (error) {
148
227
  // Silently ignore errors - custom schema is optional
149
228
  // This handles cases like:
@@ -182,6 +261,8 @@ module.exports = {
182
261
  getEncryptedModels,
183
262
  registerCustomSchema,
184
263
  loadCustomEncryptionSchema,
264
+ loadModuleEncryptionSchemas,
265
+ extractCredentialFieldsFromModules,
185
266
  validateCustomSchema,
186
- resetCustomSchema, // For testing only
267
+ resetCustomSchema,
187
268
  };
package/database/index.js CHANGED
@@ -12,13 +12,14 @@
12
12
  * etc.
13
13
  */
14
14
 
15
- const { mongoose } = require('./mongoose');
16
- const { IndividualUser } = require('./models/IndividualUser');
17
- const { OrganizationUser } = require('./models/OrganizationUser');
18
- const { UserModel } = require('./models/UserModel');
19
- const { WebsocketConnection } = require('./models/WebsocketConnection');
15
+ // Lazy-load mongoose to avoid importing mongodb when using PostgreSQL only
16
+ let _mongoose = null;
17
+ let _IndividualUser = null;
18
+ let _OrganizationUser = null;
19
+ let _UserModel = null;
20
+ let _WebsocketConnection = null;
20
21
 
21
- // Prisma exports
22
+ // Prisma exports (always available)
22
23
  const { prisma } = require('./prisma');
23
24
  const { TokenRepository } = require('../token/repositories/token-repository');
24
25
  const {
@@ -26,12 +27,38 @@ const {
26
27
  } = require('../websocket/repositories/websocket-connection-repository');
27
28
 
28
29
  module.exports = {
29
- mongoose,
30
- IndividualUser,
31
- OrganizationUser,
32
- UserModel,
33
- WebsocketConnection,
34
- // Prisma
30
+ // Lazy-loaded mongoose exports (only load when accessed)
31
+ get mongoose() {
32
+ if (!_mongoose) {
33
+ _mongoose = require('./mongoose').mongoose;
34
+ }
35
+ return _mongoose;
36
+ },
37
+ get IndividualUser() {
38
+ if (!_IndividualUser) {
39
+ _IndividualUser = require('./models/IndividualUser').IndividualUser;
40
+ }
41
+ return _IndividualUser;
42
+ },
43
+ get OrganizationUser() {
44
+ if (!_OrganizationUser) {
45
+ _OrganizationUser = require('./models/OrganizationUser').OrganizationUser;
46
+ }
47
+ return _OrganizationUser;
48
+ },
49
+ get UserModel() {
50
+ if (!_UserModel) {
51
+ _UserModel = require('./models/UserModel').UserModel;
52
+ }
53
+ return _UserModel;
54
+ },
55
+ get WebsocketConnection() {
56
+ if (!_WebsocketConnection) {
57
+ _WebsocketConnection = require('./models/WebsocketConnection').WebsocketConnection;
58
+ }
59
+ return _WebsocketConnection;
60
+ },
61
+ // Prisma (always available)
35
62
  prisma,
36
63
  TokenRepository,
37
64
  WebsocketConnectionRepository,
@@ -373,6 +373,76 @@ async function runPrismaDbPush(verbose = false, nonInteractive = false) {
373
373
  });
374
374
  }
375
375
 
376
+ /**
377
+ * Runs Prisma migrate resolve to mark a migration as applied or rolled back
378
+ * @param {string} migrationName - Name of the migration to resolve (e.g., '20251112195422_update_user_unique_constraints')
379
+ * @param {'applied'|'rolled-back'} action - Whether to mark as applied or rolled back
380
+ * @param {boolean} verbose - Enable verbose output
381
+ * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
382
+ */
383
+ async function runPrismaMigrateResolve(migrationName, action = 'applied', verbose = false) {
384
+ return new Promise((resolve) => {
385
+ try {
386
+ const schemaPath = getPrismaSchemaPath('postgresql');
387
+
388
+ // Get Prisma binary path (checks multiple locations)
389
+ const prismaBin = getPrismaBinaryPath();
390
+
391
+ // Determine args based on whether we're using direct binary or npx
392
+ const isDirectBinary = prismaBin !== 'npx prisma';
393
+ const args = isDirectBinary
394
+ ? ['migrate', 'resolve', `--${action}`, migrationName, '--schema', schemaPath]
395
+ : ['prisma', 'migrate', 'resolve', `--${action}`, migrationName, '--schema', schemaPath];
396
+
397
+ if (verbose) {
398
+ const displayCmd = isDirectBinary
399
+ ? `${prismaBin} ${args.join(' ')}`
400
+ : `npx ${args.join(' ')}`;
401
+ console.log(chalk.gray(`Running: ${displayCmd}`));
402
+ }
403
+
404
+ // Execute the command (prismaBin might be 'node /path/to/index.js' or 'npx prisma')
405
+ const [executable, ...executableArgs] = prismaBin.split(' ');
406
+ const fullArgs = [...executableArgs, ...args];
407
+
408
+ const proc = spawn(executable, fullArgs, {
409
+ stdio: 'inherit',
410
+ env: {
411
+ ...process.env,
412
+ PRISMA_HIDE_UPDATE_MESSAGE: '1'
413
+ }
414
+ });
415
+
416
+ proc.on('error', (error) => {
417
+ resolve({
418
+ success: false,
419
+ error: error.message
420
+ });
421
+ });
422
+
423
+ proc.on('close', (code) => {
424
+ if (code === 0) {
425
+ resolve({
426
+ success: true,
427
+ output: `Migration ${migrationName} marked as ${action}`
428
+ });
429
+ } else {
430
+ resolve({
431
+ success: false,
432
+ error: `Resolve process exited with code ${code}`
433
+ });
434
+ }
435
+ });
436
+
437
+ } catch (error) {
438
+ resolve({
439
+ success: false,
440
+ error: error.message
441
+ });
442
+ }
443
+ });
444
+ }
445
+
376
446
  /**
377
447
  * Determines migration command based on STAGE environment variable
378
448
  * @param {string} stage - Stage from CLI option or environment
@@ -401,6 +471,7 @@ module.exports = {
401
471
  runPrismaGenerate,
402
472
  checkDatabaseState,
403
473
  runPrismaMigrate,
474
+ runPrismaMigrateResolve,
404
475
  runPrismaDbPush,
405
476
  getMigrationCommand
406
477
  };
@@ -92,13 +92,16 @@ const loadIntegrationForWebhook = async (integrationId) => {
92
92
  integrationId
93
93
  );
94
94
 
95
- return await getIntegrationInstance.execute(
95
+ const instance = await getIntegrationInstance.execute(
96
96
  integrationId,
97
97
  integrationRecord.userId
98
98
  );
99
+
100
+ return instance;
99
101
  };
100
102
 
101
103
  const loadIntegrationForProcess = async (processId, integrationClass) => {
104
+
102
105
  const { processRepository, integrationRepository, moduleRepository } =
103
106
  initializeRepositories();
104
107
 
@@ -122,10 +125,12 @@ const loadIntegrationForProcess = async (processId, integrationClass) => {
122
125
  throw new Error(`Process not found: ${processId}`);
123
126
  }
124
127
 
125
- return await getIntegrationInstance.execute(
128
+ const instance = await getIntegrationInstance.execute(
126
129
  process.integrationId,
127
130
  process.userId
128
131
  );
132
+
133
+ return instance;
129
134
  };
130
135
 
131
136
  const createQueueWorker = (integrationClass) => {
@@ -133,18 +138,19 @@ const createQueueWorker = (integrationClass) => {
133
138
  async _run(params, context) {
134
139
  try {
135
140
  let integrationInstance;
136
- if (
137
- params.event === 'ON_WEBHOOK' &&
138
- params.data?.integrationId
139
- ) {
140
- integrationInstance = await loadIntegrationForWebhook(
141
- params.data.integrationId
142
- );
143
- } else if (params.data?.processId) {
141
+
142
+ // Prioritize processId first (for sync handler compatibility),
143
+ // then integrationId (for ANY event type that needs hydration),
144
+ // fallback to unhydrated instance
145
+ if (params.data?.processId) {
144
146
  integrationInstance = await loadIntegrationForProcess(
145
147
  params.data.processId,
146
148
  integrationClass
147
149
  );
150
+ } else if (params.data?.integrationId) {
151
+ integrationInstance = await loadIntegrationForWebhook(
152
+ params.data.integrationId
153
+ );
148
154
  } else {
149
155
  // Instantiates a DRY integration class without database records.
150
156
  // There will be cases where we need to use helpers that the api modules can export.
@@ -235,6 +235,77 @@ router.get(
235
235
  })
236
236
  );
237
237
 
238
+ /**
239
+ * POST /db-migrate/resolve
240
+ *
241
+ * Resolve a failed migration by marking it as applied or rolled back
242
+ *
243
+ * Request body:
244
+ * {
245
+ * migrationName: string (e.g., '20251112195422_update_user_unique_constraints'),
246
+ * action: 'applied' | 'rolled-back',
247
+ * stage: string (optional, defaults to STAGE env var or 'production')
248
+ * }
249
+ *
250
+ * Response (200 OK):
251
+ * {
252
+ * success: true,
253
+ * message: string,
254
+ * migrationName: string,
255
+ * action: string
256
+ * }
257
+ */
258
+ router.post(
259
+ '/db-migrate/resolve',
260
+ catchAsyncError(async (req, res) => {
261
+ const { migrationName, action = 'applied' } = req.body;
262
+
263
+ console.log(`Migration resolve request: migration=${migrationName}, action=${action}`);
264
+
265
+ // Validation
266
+ if (!migrationName) {
267
+ return res.status(400).json({
268
+ success: false,
269
+ error: 'migrationName is required'
270
+ });
271
+ }
272
+
273
+ if (!['applied', 'rolled-back'].includes(action)) {
274
+ return res.status(400).json({
275
+ success: false,
276
+ error: 'action must be either "applied" or "rolled-back"'
277
+ });
278
+ }
279
+
280
+ try {
281
+ // Import prismaRunner here to avoid circular dependencies
282
+ const prismaRunner = require('../../database/utils/prisma-runner');
283
+
284
+ const result = await prismaRunner.runPrismaMigrateResolve(migrationName, action, true);
285
+
286
+ if (!result.success) {
287
+ return res.status(500).json({
288
+ success: false,
289
+ error: `Failed to resolve migration: ${result.error}`
290
+ });
291
+ }
292
+
293
+ res.status(200).json({
294
+ success: true,
295
+ message: `Migration ${migrationName} marked as ${action}`,
296
+ migrationName,
297
+ action
298
+ });
299
+ } catch (error) {
300
+ console.error('Migration resolve failed:', error);
301
+ return res.status(500).json({
302
+ success: false,
303
+ error: error.message
304
+ });
305
+ }
306
+ })
307
+ );
308
+
238
309
  // Minimal Lambda handler (avoids app-handler-helpers which loads core/index.js → user/**)
239
310
  const serverlessHttp = require('serverless-http');
240
311
  const express = require('express');
@@ -244,7 +315,7 @@ const app = express();
244
315
  app.use(cors());
245
316
  app.use(express.json());
246
317
  app.use(router);
247
- app.use((err, req, res, next) => {
318
+ app.use((err, _req, res, _next) => {
248
319
  console.error('Migration Router Error:', err);
249
320
  res.status(500).json({ message: 'Internal Server Error' });
250
321
  });