@friggframework/core 2.0.0--canary.502.c9c05da.0 → 2.0.0--canary.498.b74f21e.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.
@@ -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) {
@@ -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,140 +122,34 @@ Or simply don't configure any encryption keys. In Production field level encrypt
122
122
 
123
123
  ## Encrypted Fields
124
124
 
125
- Core and custom encrypted fields are defined in `encryption-schema-registry.js`. See that file for the current list of encrypted fields.
125
+ Fields are defined in `encryption-schema-registry.js`:
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):**
140
- ```javascript
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
127
  ```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']
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
+ ],
188
135
  },
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'
136
+ IntegrationMapping: {
137
+ fields: ['mapping'], // Complete mapping object
138
+ },
139
+ User: {
140
+ fields: ['hashword'], // Password hash
141
+ },
142
+ Token: {
143
+ fields: ['token'], // Authentication token
220
144
  },
221
- requiredAuthMethods: {
222
- apiPropertiesToPersist: {
223
- credential: ['api_key'] // Will be encrypted automatically
224
- }
225
- }
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
- }
235
- ```
236
-
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
145
  };
249
146
  ```
250
147
 
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
148
+ ### Extending Encryption Schema
255
149
 
256
- #### Option 2: App-Level Custom Schema (Integration Developers)
150
+ #### Recommended: Custom Schema via appDefinition (Integration Developers)
257
151
 
258
- Integration developers can extend encryption without modifying core framework files.
152
+ Integration developers can extend encryption without modifying core framework files:
259
153
 
260
154
  **In `backend/index.js`:**
261
155
 
@@ -340,7 +234,7 @@ await prisma.asanaTaskMapping.create({
340
234
  FRIGG_DEBUG=1 npm run frigg:start
341
235
  ```
342
236
 
343
- #### Option 3: Modifying Core Schema (Framework Developers)
237
+ #### Advanced: Modifying Core Schema (Framework Developers)
344
238
 
345
239
  Framework developers maintaining core models can modify `encryption-schema-registry.js`:
346
240
 
@@ -19,11 +19,6 @@ 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',
27
22
  ],
28
23
  },
29
24
 
@@ -114,74 +109,6 @@ function registerCustomSchema(schema) {
114
109
  );
115
110
  }
116
111
 
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
-
185
112
  /**
186
113
  * Loads and registers custom encryption schema from appDefinition.
187
114
  * Gracefully handles cases where appDefinition is not available.
@@ -212,17 +139,11 @@ function loadCustomEncryptionSchema() {
212
139
  return; // No app definition found
213
140
  }
214
141
 
215
- // Load app-level custom schema
216
142
  const customSchema = appDefinition.encryption?.schema;
143
+
217
144
  if (customSchema && Object.keys(customSchema).length > 0) {
218
145
  registerCustomSchema(customSchema);
219
146
  }
220
-
221
- // Load module-level encryption schemas from integrations
222
- const integrations = appDefinition.integrations;
223
- if (integrations && Array.isArray(integrations)) {
224
- loadModuleEncryptionSchemas(integrations);
225
- }
226
147
  } catch (error) {
227
148
  // Silently ignore errors - custom schema is optional
228
149
  // This handles cases like:
@@ -261,8 +182,6 @@ module.exports = {
261
182
  getEncryptedModels,
262
183
  registerCustomSchema,
263
184
  loadCustomEncryptionSchema,
264
- loadModuleEncryptionSchemas,
265
- extractCredentialFieldsFromModules,
266
185
  validateCustomSchema,
267
- resetCustomSchema,
186
+ resetCustomSchema, // For testing only
268
187
  };
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
  });
@@ -203,22 +203,53 @@ class IntegrationBase {
203
203
 
204
204
  /**
205
205
  * Returns the modules as object with keys as module names.
206
+ * Uses the keys from Definition.modules to attach modules correctly.
207
+ *
208
+ * Example:
209
+ * Definition.modules = { attio: {...}, quo: { definition: { getName: () => 'quo-attio' } } }
210
+ * Module with getName()='quo-attio' gets attached as this.quo (not this['quo-attio'])
211
+ *
206
212
  * @private
207
213
  * @param {Array} integrationModules - Array of module instances
208
214
  * @returns {Object} The modules object
209
215
  */
210
216
  _appendModules(integrationModules) {
211
217
  const modules = {};
218
+
219
+ // Build reverse mapping: definition.getName() → referenceKey
220
+ // e.g., 'quo-attio' → 'quo', 'attio' → 'attio'
221
+ const moduleNameToKey = {};
222
+ if (this.constructor.Definition?.modules) {
223
+ for (const [key, moduleConfig] of Object.entries(this.constructor.Definition.modules)) {
224
+ const definition = moduleConfig.definition;
225
+ if (definition) {
226
+ // Use getName() if available, fallback to moduleName
227
+ const definitionName = typeof definition.getName === 'function'
228
+ ? definition.getName()
229
+ : definition.moduleName;
230
+ if (definitionName) {
231
+ moduleNameToKey[definitionName] = key;
232
+ }
233
+ }
234
+ }
235
+ }
236
+
212
237
  for (const module of integrationModules) {
213
- const key =
238
+ const moduleName =
214
239
  typeof module.getName === 'function'
215
240
  ? module.getName()
216
241
  : module.name;
242
+
243
+ // Use the reference key from Definition.modules if available,
244
+ // otherwise fall back to moduleName
245
+ const key = moduleNameToKey[moduleName] || moduleName;
246
+
217
247
  if (key) {
218
248
  modules[key] = module;
219
249
  this[key] = module;
220
250
  }
221
251
  }
252
+
222
253
  return modules;
223
254
  }
224
255
 
@@ -333,7 +364,6 @@ class IntegrationBase {
333
364
  return {};
334
365
  }
335
366
  async loadUserActions({ actionType } = {}) {
336
- console.log('loadUserActions called with actionType:', actionType);
337
367
  const userActions = {};
338
368
  for (const [key, event] of Object.entries(this.events)) {
339
369
  if (event.type === constantsToBeMigrated.types.USER_ACTION) {
@@ -389,7 +419,6 @@ class IntegrationBase {
389
419
 
390
420
  async onWebhook({ data }) {
391
421
  // Default: no-op, integrations override this
392
- console.log('Webhook received:', data);
393
422
  }
394
423
 
395
424
  async queueWebhook(data) {