@friggframework/core 2.0.0--canary.490.883b991.0 → 2.0.0--canary.497.a3f25f9.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.
Files changed (53) hide show
  1. package/associations/model.js +54 -0
  2. package/core/create-handler.js +12 -63
  3. package/database/index.js +29 -7
  4. package/database/models/UserModel.js +7 -0
  5. package/database/models/WebsocketConnection.js +55 -0
  6. package/database/prisma.js +19 -51
  7. package/database/repositories/health-check-repository-mongodb.js +23 -23
  8. package/database/repositories/health-check-repository.js +108 -0
  9. package/database/utils/mongodb-collection-utils.js +18 -26
  10. package/database/utils/mongodb-schema-init.js +12 -7
  11. package/generated/prisma-mongodb/edge.js +6 -6
  12. package/generated/prisma-mongodb/index-browser.js +4 -4
  13. package/generated/prisma-mongodb/index.d.ts +2 -3
  14. package/generated/prisma-mongodb/index.js +6 -6
  15. package/generated/prisma-mongodb/package.json +1 -1
  16. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  17. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  18. package/generated/prisma-mongodb/runtime/binary.js +2 -2
  19. package/generated/prisma-mongodb/runtime/edge-esm.js +3 -3
  20. package/generated/prisma-mongodb/runtime/edge.js +3 -3
  21. package/generated/prisma-mongodb/runtime/library.d.ts +0 -5
  22. package/generated/prisma-mongodb/runtime/react-native.js +14 -14
  23. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +18 -18
  24. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +11 -11
  25. package/generated/prisma-mongodb/wasm.js +6 -6
  26. package/generated/prisma-postgresql/edge.js +6 -6
  27. package/generated/prisma-postgresql/index-browser.js +4 -4
  28. package/generated/prisma-postgresql/index.d.ts +2 -3
  29. package/generated/prisma-postgresql/index.js +6 -6
  30. package/generated/prisma-postgresql/package.json +1 -1
  31. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  32. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  33. package/generated/prisma-postgresql/query_engine_bg.js +2 -2
  34. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  35. package/generated/prisma-postgresql/runtime/binary.js +2 -2
  36. package/generated/prisma-postgresql/runtime/edge-esm.js +3 -3
  37. package/generated/prisma-postgresql/runtime/edge.js +3 -3
  38. package/generated/prisma-postgresql/runtime/library.d.ts +0 -5
  39. package/generated/prisma-postgresql/runtime/react-native.js +14 -14
  40. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +18 -18
  41. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +11 -11
  42. package/generated/prisma-postgresql/wasm.js +6 -6
  43. package/handlers/backend-utils.js +9 -8
  44. package/integrations/integration-router.js +1 -0
  45. package/integrations/use-cases/delete-integration-for-user.js +31 -3
  46. package/integrations/use-cases/get-integrations-for-user.js +5 -4
  47. package/integrations/utils/map-integration-dto.js +2 -1
  48. package/modules/entity.js +46 -0
  49. package/modules/index.js +2 -0
  50. package/package.json +5 -5
  51. package/syncs/model.js +62 -0
  52. package/database/PRISMA_BEST_PRACTICES.md +0 -654
  53. package/database/utils/documentdb-compatibility.js +0 -63
@@ -0,0 +1,54 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const schema = new mongoose.Schema({
4
+ integration: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: "Integration",
7
+ required: true,
8
+ },
9
+ name: { type: String, required: true },
10
+ type: {
11
+ type: String,
12
+ enum: ["ONE_TO_MANY", "ONE_TO_ONE", "MANY_TO_ONE"],
13
+ required: true,
14
+ },
15
+ primaryObject: { type: String, required: true },
16
+ objects: [
17
+ {
18
+ entity: {
19
+ type: mongoose.Schema.Types.ObjectId,
20
+ ref: "Entity",
21
+ required: true,
22
+ },
23
+ objectType: { type: String, required: true },
24
+ objId: { type: String, required: true },
25
+ metadata: { type: Object, required: false },
26
+ },
27
+ ],
28
+ });
29
+
30
+ schema.statics({
31
+ addAssociation: async function (id, object) {
32
+ return this.update({ _id: id }, { $push: { objects: object } });
33
+ },
34
+ findAssociation: async function (name, dataIdentifierHash) {
35
+ const syncList = await this.list({
36
+ name: name,
37
+ "dataIdentifiers.hash": dataIdentifierHash,
38
+ });
39
+
40
+ if (syncList.length === 1) {
41
+ return syncList[0];
42
+ } else if (syncList.length === 0) {
43
+ return null;
44
+ } else {
45
+ throw new Error(
46
+ `there are multiple sync objects with the name ${name}, for entities [${entities}]`
47
+ );
48
+ }
49
+ },
50
+ });
51
+
52
+ const Association =
53
+ mongoose.models.Association || mongoose.model("Association", schema);
54
+ module.exports = { Association };
@@ -1,76 +1,21 @@
1
+ // This line should be at the top of the webpacked output, so be sure to require createHandler first in any handlers. "Soon" sourcemaps will be built into Node... after that, this package won't be needed.
2
+ // REMOVING FOR NOW UNTIL WE ADD WEBPACK BACK IN
3
+ // require('source-map-support').install();
4
+
1
5
  const { initDebugLog, flushDebugLog } = require('../logs');
2
6
  const { secretsToEnv } = require('./secrets-to-env');
3
7
 
4
- let databaseInitializer = null;
5
- let databaseInitialized = false;
6
- let databaseInitPromise = null;
7
-
8
- function setDatabaseInitializer(initializer) {
9
- databaseInitializer = initializer;
10
- }
11
-
12
- function getDefaultDatabaseInitializer() {
13
- if (!databaseInitializer) {
14
- const { connectPrisma } = require('../database/prisma');
15
- databaseInitializer = connectPrisma;
16
- }
17
- return databaseInitializer;
18
- }
19
-
20
- async function ensureDatabaseInitialized() {
21
- if (databaseInitialized) {
22
- return;
23
- }
24
-
25
- if (!databaseInitPromise) {
26
- databaseInitPromise = (async () => {
27
- try {
28
- const initializer = getDefaultDatabaseInitializer();
29
- await initializer();
30
- databaseInitialized = true;
31
- console.log('✓ Database initialized successfully');
32
- } catch (error) {
33
- console.error('Failed to initialize database:', error.message);
34
- databaseInitPromise = null;
35
- throw error;
36
- }
37
- })();
38
- }
39
-
40
- return databaseInitPromise;
41
- }
42
-
43
- /**
44
- * Creates a Lambda handler with database initialization.
45
- *
46
- * Follows hexagonal architecture: database initialization is a port that can be injected.
47
- * Default adapter uses Prisma's connectPrisma(), which handles both PostgreSQL and MongoDB.
48
- *
49
- * @param {Object} options
50
- * @param {string} options.eventName - Event name for logging
51
- * @param {boolean} options.isUserFacingResponse - Whether to hide error details (default: true)
52
- * @param {Function} options.method - The handler method to execute
53
- * @param {boolean} options.shouldUseDatabase - Whether to initialize database (default: true)
54
- * @param {Function} options.databaseInitializer - Optional custom database initializer
55
- * @returns {Function} Lambda handler
56
- */
57
8
  const createHandler = (optionByName = {}) => {
58
9
  const {
59
10
  eventName = 'Event',
60
11
  isUserFacingResponse = true,
61
12
  method,
62
- shouldUseDatabase = true,
63
- databaseInitializer: customInitializer = null,
64
13
  } = optionByName;
65
14
 
66
15
  if (!method) {
67
16
  throw new Error('Method is required for handler.');
68
17
  }
69
18
 
70
- if (customInitializer) {
71
- setDatabaseInitializer(customInitializer);
72
- }
73
-
74
19
  return async (event, context) => {
75
20
  try {
76
21
  initDebugLog(eventName, event);
@@ -81,18 +26,18 @@ const createHandler = (optionByName = {}) => {
81
26
  console.info(`${requestMethod} ${requestPath}`);
82
27
  }
83
28
 
29
+ // If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
84
30
  await secretsToEnv();
85
31
 
86
- if (shouldUseDatabase) {
87
- await ensureDatabaseInitialized();
88
- }
89
-
32
+ // Helps mongoose reuse the connection. Lowers response times.
90
33
  context.callbackWaitsForEmptyEventLoop = false;
91
34
 
35
+ // Run the Lambda
92
36
  return await method(event, context);
93
37
  } catch (error) {
94
38
  flushDebugLog(error);
95
39
 
40
+ // Don't leak implementation details to end users.
96
41
  if (isUserFacingResponse) {
97
42
  return {
98
43
  statusCode: 500,
@@ -102,10 +47,14 @@ const createHandler = (optionByName = {}) => {
102
47
  };
103
48
  }
104
49
 
50
+ // Handle server-to-server responses.
51
+
52
+ // Halt errors are logged but suceed and won't be retried.
105
53
  if (error.isHaltError === true) {
106
54
  return;
107
55
  }
108
56
 
57
+ // Here we can just rethrow and let AWS build the response.
109
58
  throw error;
110
59
  }
111
60
  };
package/database/index.js CHANGED
@@ -1,16 +1,38 @@
1
+ //todo: probably most of this file content can be removed
2
+
1
3
  /**
2
4
  * Database Module Index
3
- *
4
- * Exports Prisma client and repositories following hexagonal architecture.
5
- * Use repositories for data access - never access Prisma directly from domain layer.
5
+ * Exports Mongoose models and connection utilities
6
+ *
7
+ * Note: Frigg uses the Repository pattern for data access.
8
+ * Models are not meant to be used directly - use repositories instead:
9
+ * - SyncRepository (syncs/sync-repository.js)
10
+ * - IntegrationRepository (integrations/integration-repository.js)
11
+ * - CredentialRepository (credential/credential-repository.js)
12
+ * etc.
6
13
  */
7
14
 
8
- const { prisma, connectPrisma, disconnectPrisma } = require('./prisma');
9
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');
20
+
21
+ // Prisma exports
22
+ const { prisma } = require('./prisma');
23
+ const { TokenRepository } = require('../token/repositories/token-repository');
24
+ const {
25
+ WebsocketConnectionRepository,
26
+ } = require('../websocket/repositories/websocket-connection-repository');
10
27
 
11
28
  module.exports = {
29
+ mongoose,
30
+ IndividualUser,
31
+ OrganizationUser,
32
+ UserModel,
33
+ WebsocketConnection,
34
+ // Prisma
12
35
  prisma,
13
- connectPrisma,
14
- disconnectPrisma,
15
- mongoose, // Still used for legacy ping operations in some health checks
36
+ TokenRepository,
37
+ WebsocketConnectionRepository,
16
38
  };
@@ -0,0 +1,7 @@
1
+ const { mongoose } = require('../mongoose');
2
+
3
+ const schema = new mongoose.Schema({}, {timestamps: true})
4
+
5
+ const UserModel = mongoose.models.User || mongoose.model('User',schema)
6
+
7
+ module.exports = { UserModel: UserModel };
@@ -0,0 +1,55 @@
1
+ const { mongoose } = require('../mongoose');
2
+ const {
3
+ ApiGatewayManagementApiClient,
4
+ PostToConnectionCommand,
5
+ } = require('@aws-sdk/client-apigatewaymanagementapi');
6
+
7
+ const schema = new mongoose.Schema({
8
+ connectionId: { type: mongoose.Schema.Types.String },
9
+ });
10
+
11
+ // Add a static method to get active connections
12
+ schema.statics.getActiveConnections = async function () {
13
+ try {
14
+ // Return empty array if websockets are not configured
15
+ if (!process.env.WEBSOCKET_API_ENDPOINT) {
16
+ return [];
17
+ }
18
+
19
+ const connections = await this.find({}, 'connectionId');
20
+ return connections.map((conn) => ({
21
+ connectionId: conn.connectionId,
22
+ send: async (data) => {
23
+ const apigwManagementApi = new ApiGatewayManagementApiClient({
24
+ endpoint: process.env.WEBSOCKET_API_ENDPOINT,
25
+ });
26
+
27
+ try {
28
+ const command = new PostToConnectionCommand({
29
+ ConnectionId: conn.connectionId,
30
+ Data: JSON.stringify(data),
31
+ });
32
+ await apigwManagementApi.send(command);
33
+ } catch (error) {
34
+ if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
35
+ console.log(`Stale connection ${conn.connectionId}`);
36
+ await this.deleteOne({
37
+ connectionId: conn.connectionId,
38
+ });
39
+ } else {
40
+ throw error;
41
+ }
42
+ }
43
+ },
44
+ }));
45
+ } catch (error) {
46
+ console.error('Error getting active connections:', error);
47
+ throw error;
48
+ }
49
+ };
50
+
51
+ const WebsocketConnection =
52
+ mongoose.models.WebsocketConnection ||
53
+ mongoose.model('WebsocketConnection', schema);
54
+
55
+ module.exports = { WebsocketConnection };
@@ -14,40 +14,22 @@ const config = require('./config');
14
14
  * Note: This should only be called when DB_TYPE is 'mongodb'
15
15
  */
16
16
  function ensureMongoDbUrl() {
17
- let connectionString;
18
-
17
+ // If DATABASE_URL is already set, use it
19
18
  if (process.env.DATABASE_URL && process.env.DATABASE_URL.trim()) {
20
- connectionString = process.env.DATABASE_URL;
21
- } else if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
22
- connectionString = process.env.MONGO_URI;
23
- logger.debug('Using MONGO_URI as DATABASE_URL for MongoDB connection');
24
- } else {
25
- throw new Error(
26
- 'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB'
27
- );
19
+ return;
28
20
  }
29
-
30
- // Ensure readPreference=primary for transaction support
31
- // MongoDB requires this for all operations within transactions
32
- try {
33
- const url = new URL(connectionString);
34
- const params = url.searchParams;
35
-
36
- const existingReadPreference = params.get('readPreference');
37
- if (existingReadPreference && existingReadPreference !== 'primary') {
38
- params.set('readPreference', 'primary');
39
- logger.debug(`Overrode readPreference from ${existingReadPreference} to primary for transaction support`);
40
- } else if (!existingReadPreference) {
41
- params.set('readPreference', 'primary');
42
- logger.debug('Added readPreference=primary to MongoDB connection string for transaction support');
43
- }
44
-
45
- connectionString = url.toString();
46
- } catch (error) {
47
- logger.warn('Failed to parse MongoDB connection string as URL, skipping readPreference configuration:', error.message);
21
+
22
+ // Fallback to MONGO_URI for backwards compatibility with DocumentDB deployments
23
+ if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
24
+ process.env.DATABASE_URL = process.env.MONGO_URI;
25
+ logger.debug('Using MONGO_URI as DATABASE_URL for MongoDB connection');
26
+ return;
48
27
  }
49
-
50
- process.env.DATABASE_URL = connectionString;
28
+
29
+ // Neither is set - error
30
+ throw new Error(
31
+ 'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB'
32
+ );
51
33
  }
52
34
 
53
35
  function getEncryptionConfig() {
@@ -122,27 +104,14 @@ const prismaClientSingleton = () => {
122
104
 
123
105
  // Helper to try loading Prisma client from multiple locations
124
106
  const loadPrismaClient = (dbType) => {
125
- const fs = require('fs');
126
- const layerPath = `/opt/nodejs/node_modules/generated/prisma-${dbType}`;
127
-
128
- // Check if layer exists (when usePrismaLambdaLayer=true)
129
- if (fs.existsSync(layerPath)) {
130
- try {
131
- return require(layerPath).PrismaClient;
132
- } catch (err) {
133
- // Layer exists but failed to load - fall through to bundled paths
134
- }
135
- }
136
-
137
- // Layer doesn't exist or failed - use bundled/local paths (when usePrismaLambdaLayer=false)
138
- const localPaths = [
139
- // Bundled with function (when usePrismaLambdaLayer=false)
140
- `@friggframework/core/generated/prisma-${dbType}`,
141
- // Local development (relative to core package)
107
+ const paths = [
108
+ // Lambda layer location (when using Prisma Lambda layer)
109
+ `/opt/nodejs/node_modules/generated/prisma-${dbType}`,
110
+ // Local development location (relative to core package)
142
111
  `../generated/prisma-${dbType}`,
143
112
  ];
144
113
 
145
- for (const path of localPaths) {
114
+ for (const path of paths) {
146
115
  try {
147
116
  return require(path).PrismaClient;
148
117
  } catch (err) {
@@ -151,8 +120,7 @@ const prismaClientSingleton = () => {
151
120
  }
152
121
 
153
122
  throw new Error(
154
- `Cannot find Prisma client for ${dbType}. ` +
155
- `Tried layer path: ${layerPath}, local paths: ${localPaths.join(', ')}`
123
+ `Cannot find Prisma client for ${dbType}. Tried paths: ${paths.join(', ')}`
156
124
  );
157
125
  };
158
126
 
@@ -2,7 +2,6 @@ const { mongoose } = require('../mongoose');
2
2
  const {
3
3
  HealthCheckRepositoryInterface,
4
4
  } = require('./health-check-repository-interface');
5
- const { removeUndefinedValues } = require('../utils/documentdb-compatibility');
6
5
 
7
6
  class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
8
7
  /**
@@ -30,30 +29,38 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
30
29
  }
31
30
 
32
31
  return {
32
+ readyState: isConnected ? 1 : 0,
33
33
  readyState: isConnected ? 1 : 0,
34
34
  stateName,
35
35
  isConnected,
36
36
  };
37
37
  }
38
38
 
39
- /**
40
- * @param {number} maxTimeMS
41
- * @returns {Promise<number>} Response time in milliseconds
42
- */
43
39
  async pingDatabase(maxTimeMS = 2000) {
44
40
  const pingStart = Date.now();
45
- await this.prisma.$queryRaw`SELECT 1`.catch(() => {
46
- return this.prisma.$runCommandRaw({ ping: 1 });
47
- });
41
+
42
+ // Create a timeout promise that rejects after maxTimeMS
43
+ const timeoutPromise = new Promise((_, reject) =>
44
+ setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
45
+ );
46
+
47
+ // Race between the database ping and the timeout
48
+ await Promise.race([
49
+ prisma.$queryRaw`SELECT 1`.catch(() => {
50
+ // For MongoDB, use runCommandRaw instead
51
+ return prisma.$runCommandRaw({ ping: 1 });
52
+ }),
53
+ timeoutPromise
54
+ ]);
55
+
56
+ return Date.now() - pingStart;
57
+ }
48
58
  return Date.now() - pingStart;
49
59
  }
50
60
 
51
61
  async createCredential(credentialData) {
52
- // Remove undefined values to prevent Prisma from using $$REMOVE (DocumentDB unsupported)
53
- const cleanedData = removeUndefinedValues(credentialData);
54
-
55
62
  return await this.prisma.credential.create({
56
- data: cleanedData,
63
+ data: credentialData,
57
64
  });
58
65
  }
59
66
 
@@ -68,17 +75,10 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
68
75
  * @returns {Promise<Object|null>}
69
76
  */
70
77
  async getRawCredentialById(id) {
71
- const result = await this.prisma.$runCommandRaw({
72
- find: 'Credential',
73
- filter: { _id: { $oid: id } },
74
- limit: 1,
75
- });
76
-
77
- if (!result.cursor || !result.cursor.firstBatch || result.cursor.firstBatch.length === 0) {
78
- return null;
79
- }
80
-
81
- return result.cursor.firstBatch[0];
78
+ const { ObjectId } = require('mongodb');
79
+ return await mongoose.connection.db
80
+ .collection('Credential')
81
+ .findOne({ _id: new ObjectId(id) });
82
82
  }
83
83
 
84
84
  async deleteCredential(id) {
@@ -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 };
@@ -5,14 +5,11 @@
5
5
  * handling the constraint that collections cannot be created inside
6
6
  * multi-document transactions.
7
7
  *
8
- * Uses Prisma's $runCommandRaw to send native MongoDB commands directly,
9
- * avoiding the need for mongoose connection.
10
- *
11
8
  * @see https://github.com/prisma/prisma/issues/8305
12
9
  * @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
13
10
  */
14
11
 
15
- const { prisma } = require('../prisma');
12
+ const { mongoose } = require('../mongoose');
16
13
 
17
14
  /**
18
15
  * Ensures a MongoDB collection exists
@@ -21,9 +18,6 @@ const { prisma } = require('../prisma');
21
18
  * transactions. This function checks if a collection exists and creates it if needed,
22
19
  * preventing "Cannot create namespace in multi-document transaction" errors.
23
20
  *
24
- * Uses Prisma's $runCommandRaw to send native MongoDB commands, which works with
25
- * both MongoDB and DocumentDB.
26
- *
27
21
  * @param {string} collectionName - Name of the collection to ensure exists
28
22
  * @returns {Promise<void>}
29
23
  *
@@ -36,24 +30,23 @@ const { prisma } = require('../prisma');
36
30
  */
37
31
  async function ensureCollectionExists(collectionName) {
38
32
  try {
39
- // Check if collection exists using Prisma's $runCommandRaw
40
- const collections = await prisma.$runCommandRaw({
41
- listCollections: 1,
42
- filter: { name: collectionName }
43
- });
33
+ const collections = await mongoose.connection.db
34
+ .listCollections({ name: collectionName })
35
+ .toArray();
44
36
 
45
- if (collections.cursor.firstBatch.length === 0) {
46
- // Collection doesn't exist, create it
47
- await prisma.$runCommandRaw({
48
- create: collectionName
49
- });
50
- console.log(`✓ Created collection: ${collectionName}`);
37
+ if (collections.length === 0) {
38
+ // Collection doesn't exist, create it outside of any transaction
39
+ await mongoose.connection.db.createCollection(collectionName);
40
+ console.log(`Created MongoDB collection: ${collectionName}`);
51
41
  }
52
42
  } catch (error) {
53
- // Ignore "already exists" errors (code 48: NamespaceExists)
54
- if (error.code !== 48 && error.codeName !== 'NamespaceExists') {
55
- console.warn(`Warning: Could not ensure collection ${collectionName}:`, error.message);
43
+ // Collection might already exist due to race condition, or other error
44
+ // Log warning but don't fail - let subsequent operations handle errors
45
+ if (error.codeName === 'NamespaceExists') {
46
+ // This is expected in race conditions, silently continue
47
+ return;
56
48
  }
49
+ console.warn(`Error ensuring collection ${collectionName} exists:`, error.message);
57
50
  }
58
51
  }
59
52
 
@@ -80,12 +73,11 @@ async function ensureCollectionsExist(collectionNames) {
80
73
  */
81
74
  async function collectionExists(collectionName) {
82
75
  try {
83
- const collections = await prisma.$runCommandRaw({
84
- listCollections: 1,
85
- filter: { name: collectionName }
86
- });
76
+ const collections = await mongoose.connection.db
77
+ .listCollections({ name: collectionName })
78
+ .toArray();
87
79
 
88
- return collections.cursor.firstBatch.length > 0;
80
+ return collections.length > 0;
89
81
  } catch (error) {
90
82
  console.error(`Error checking if collection ${collectionName} exists:`, error.message);
91
83
  return false;