@friggframework/core 2.0.0-next.53 → 2.0.0-next.54

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 (56) hide show
  1. package/CLAUDE.md +2 -1
  2. package/application/commands/integration-commands.js +1 -1
  3. package/application/index.js +1 -1
  4. package/credential/repositories/credential-repository-documentdb.js +300 -0
  5. package/credential/repositories/credential-repository-factory.js +8 -1
  6. package/database/config.js +4 -4
  7. package/database/documentdb-encryption-service.js +330 -0
  8. package/database/documentdb-utils.js +136 -0
  9. package/database/encryption/README.md +50 -0
  10. package/database/encryption/documentdb-encryption-service.md +3270 -0
  11. package/database/encryption/encryption-schema-registry.js +46 -0
  12. package/database/prisma.js +7 -47
  13. package/database/repositories/health-check-repository-documentdb.js +134 -0
  14. package/database/repositories/health-check-repository-factory.js +6 -1
  15. package/database/repositories/health-check-repository-interface.js +29 -34
  16. package/database/repositories/health-check-repository-mongodb.js +1 -3
  17. package/database/use-cases/check-database-state-use-case.js +3 -3
  18. package/database/use-cases/run-database-migration-use-case.js +6 -4
  19. package/database/use-cases/trigger-database-migration-use-case.js +2 -2
  20. package/database/utils/mongodb-schema-init.js +5 -5
  21. package/database/utils/prisma-runner.js +15 -9
  22. package/generated/prisma-mongodb/edge.js +3 -3
  23. package/generated/prisma-mongodb/index.d.ts +10 -4
  24. package/generated/prisma-mongodb/index.js +3 -3
  25. package/generated/prisma-mongodb/package.json +1 -1
  26. package/generated/prisma-mongodb/schema.prisma +1 -3
  27. package/generated/prisma-mongodb/wasm.js +2 -2
  28. package/generated/prisma-postgresql/edge.js +3 -3
  29. package/generated/prisma-postgresql/index.d.ts +10 -4
  30. package/generated/prisma-postgresql/index.js +3 -3
  31. package/generated/prisma-postgresql/package.json +1 -1
  32. package/generated/prisma-postgresql/schema.prisma +1 -3
  33. package/generated/prisma-postgresql/wasm.js +2 -2
  34. package/handlers/routers/db-migration.js +2 -3
  35. package/handlers/routers/health.js +0 -3
  36. package/handlers/workers/db-migration.js +8 -8
  37. package/integrations/repositories/integration-mapping-repository-documentdb.js +135 -0
  38. package/integrations/repositories/integration-mapping-repository-factory.js +8 -1
  39. package/integrations/repositories/integration-repository-documentdb.js +189 -0
  40. package/integrations/repositories/integration-repository-factory.js +8 -1
  41. package/integrations/repositories/process-repository-documentdb.js +141 -0
  42. package/integrations/repositories/process-repository-factory.js +8 -1
  43. package/modules/repositories/module-repository-documentdb.js +307 -0
  44. package/modules/repositories/module-repository-factory.js +8 -1
  45. package/package.json +5 -5
  46. package/prisma-mongodb/schema.prisma +1 -3
  47. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +69 -0
  48. package/prisma-postgresql/schema.prisma +1 -3
  49. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  50. package/syncs/repositories/sync-repository-factory.js +6 -1
  51. package/token/repositories/token-repository-documentdb.js +125 -0
  52. package/token/repositories/token-repository-factory.js +8 -1
  53. package/user/repositories/user-repository-documentdb.js +292 -0
  54. package/user/repositories/user-repository-factory.js +6 -1
  55. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  56. package/websocket/repositories/websocket-connection-repository-factory.js +8 -1
@@ -109,6 +109,51 @@ function registerCustomSchema(schema) {
109
109
  );
110
110
  }
111
111
 
112
+ /**
113
+ * Loads and registers custom encryption schema from appDefinition.
114
+ * Gracefully handles cases where appDefinition is not available.
115
+ *
116
+ * This ensures that custom encryption schemas defined in the backend's index.js
117
+ * are registered before any repositories attempt to encrypt data.
118
+ *
119
+ * Used by both Prisma (MongoDB/PostgreSQL) and DocumentDB encryption services.
120
+ */
121
+ function loadCustomEncryptionSchema() {
122
+ try {
123
+ // Lazy require to avoid circular dependency issues
124
+ const path = require('node:path');
125
+ const { findNearestBackendPackageJson } = require('../../utils');
126
+
127
+ const backendPackagePath = findNearestBackendPackageJson();
128
+ if (!backendPackagePath) {
129
+ return; // No backend found, skip custom schema
130
+ }
131
+
132
+ const backendDir = path.dirname(backendPackagePath);
133
+ const backendIndexPath = path.join(backendDir, 'index.js');
134
+
135
+ const backendModule = require(backendIndexPath);
136
+ const appDefinition = backendModule?.Definition;
137
+
138
+ if (!appDefinition) {
139
+ return; // No app definition found
140
+ }
141
+
142
+ const customSchema = appDefinition.encryption?.schema;
143
+
144
+ if (customSchema && Object.keys(customSchema).length > 0) {
145
+ registerCustomSchema(customSchema);
146
+ }
147
+ } catch (error) {
148
+ // Silently ignore errors - custom schema is optional
149
+ // This handles cases like:
150
+ // - Backend package.json not found (tests, standalone usage)
151
+ // - No appDefinition defined
152
+ // - No custom encryption schema specified
153
+ logger.debug('Could not load custom encryption schema:', error.message);
154
+ }
155
+ }
156
+
112
157
  function getEncryptedFields(modelName) {
113
158
  const coreFields = CORE_ENCRYPTION_SCHEMA[modelName]?.fields || [];
114
159
  const customFields = customSchema[modelName]?.fields || [];
@@ -136,6 +181,7 @@ module.exports = {
136
181
  hasEncryptedFields,
137
182
  getEncryptedModels,
138
183
  registerCustomSchema,
184
+ loadCustomEncryptionSchema,
139
185
  validateCustomSchema,
140
186
  resetCustomSchema, // For testing only
141
187
  };
@@ -1,7 +1,7 @@
1
1
  const {
2
2
  createEncryptionExtension,
3
3
  } = require('./encryption/prisma-encryption-extension');
4
- const { registerCustomSchema } = require('./encryption/encryption-schema-registry');
4
+ const { loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
5
5
  const { logger } = require('./encryption/logger');
6
6
  const { Cryptor } = require('../encrypt/Cryptor');
7
7
  const config = require('./config');
@@ -11,7 +11,7 @@ const config = require('./config');
11
11
  * Falls back to MONGO_URI if DATABASE_URL is not set
12
12
  * Infrastructure layer concern - maps legacy MONGO_URI to Prisma's expected DATABASE_URL
13
13
  *
14
- * Note: This should only be called when DB_TYPE is 'mongodb'
14
+ * Note: This should only be called when DB_TYPE is 'mongodb' or 'documentdb'
15
15
  */
16
16
  function ensureMongoDbUrl() {
17
17
  // If DATABASE_URL is already set, use it
@@ -22,13 +22,13 @@ function ensureMongoDbUrl() {
22
22
  // Fallback to MONGO_URI for backwards compatibility with DocumentDB deployments
23
23
  if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
24
24
  process.env.DATABASE_URL = process.env.MONGO_URI;
25
- logger.debug('Using MONGO_URI as DATABASE_URL for MongoDB connection');
25
+ logger.debug('Using MONGO_URI as DATABASE_URL for Mongo-compatible connection');
26
26
  return;
27
27
  }
28
28
 
29
29
  // Neither is set - error
30
30
  throw new Error(
31
- 'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB'
31
+ 'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB/DocumentDB'
32
32
  );
33
33
  }
34
34
 
@@ -59,46 +59,6 @@ function getEncryptionConfig() {
59
59
  };
60
60
  }
61
61
 
62
- /**
63
- * Loads and registers custom encryption schema from appDefinition
64
- * Gracefully handles cases where appDefinition is not available
65
- */
66
- function loadCustomEncryptionSchema() {
67
- try {
68
- // Lazy require to avoid circular dependency issues
69
- const path = require('node:path');
70
- const { findNearestBackendPackageJson } = require('../utils');
71
-
72
- const backendPackagePath = findNearestBackendPackageJson();
73
- if (!backendPackagePath) {
74
- return; // No backend found, skip custom schema
75
- }
76
-
77
- const backendDir = path.dirname(backendPackagePath);
78
- const backendIndexPath = path.join(backendDir, 'index.js');
79
-
80
- const backendModule = require(backendIndexPath);
81
- const appDefinition = backendModule?.Definition;
82
-
83
- if (!appDefinition) {
84
- return; // No app definition found
85
- }
86
-
87
- const customSchema = appDefinition.encryption?.schema;
88
-
89
- if (customSchema && Object.keys(customSchema).length > 0) {
90
- registerCustomSchema(customSchema);
91
- }
92
- } catch (error) {
93
- // Silently ignore errors - custom schema is optional
94
- // This handles cases like:
95
- // - Backend package.json not found (tests, standalone usage)
96
- // - No appDefinition defined
97
- // - No custom encryption schema specified
98
- logger.debug('Could not load custom encryption schema:', error.message);
99
- }
100
- }
101
-
102
62
  const prismaClientSingleton = () => {
103
63
  let PrismaClient;
104
64
 
@@ -124,7 +84,7 @@ const prismaClientSingleton = () => {
124
84
  );
125
85
  };
126
86
 
127
- if (config.DB_TYPE === 'mongodb') {
87
+ if (config.DB_TYPE === 'mongodb' || config.DB_TYPE === 'documentdb') {
128
88
  // Ensure DATABASE_URL is set (fallback to MONGO_URI if needed)
129
89
  ensureMongoDbUrl();
130
90
  PrismaClient = loadPrismaClient('mongodb');
@@ -132,7 +92,7 @@ const prismaClientSingleton = () => {
132
92
  PrismaClient = loadPrismaClient('postgresql');
133
93
  } else {
134
94
  throw new Error(
135
- `Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'postgresql'`
95
+ `Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
136
96
  );
137
97
  }
138
98
 
@@ -205,7 +165,7 @@ async function connectPrisma() {
205
165
  // Initialize MongoDB schema - ensure all collections exist
206
166
  // Only run for MongoDB/DocumentDB (not PostgreSQL)
207
167
  // This prevents "Cannot create namespace in multi-document transaction" errors
208
- if (config.DB_TYPE === 'mongodb') {
168
+ if (config.DB_TYPE === 'mongodb' || config.DB_TYPE === 'documentdb') {
209
169
  const { initializeMongoDBSchema } = require('./utils/mongodb-schema-init');
210
170
  await initializeMongoDBSchema();
211
171
  }
@@ -0,0 +1,134 @@
1
+ const {
2
+ HealthCheckRepositoryInterface,
3
+ } = require('./health-check-repository-interface');
4
+ const {
5
+ toObjectId,
6
+ fromObjectId,
7
+ findOne,
8
+ insertOne,
9
+ deleteOne,
10
+ } = require('../documentdb-utils');
11
+ const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
12
+
13
+ class HealthCheckRepositoryDocumentDB extends HealthCheckRepositoryInterface {
14
+ /**
15
+ * @param {Object} params
16
+ * @param {Object} params.prismaClient - Prisma client instance
17
+ */
18
+ constructor({ prismaClient }) {
19
+ super();
20
+ this.prisma = prismaClient;
21
+ this.encryptionService = new DocumentDBEncryptionService();
22
+ }
23
+
24
+ /**
25
+ * @returns {Promise<{readyState: number, stateName: string, isConnected: boolean}>}
26
+ */
27
+ async getDatabaseConnectionState() {
28
+ let isConnected = false;
29
+ let stateName = 'unknown';
30
+
31
+ try {
32
+ await this.prisma.$runCommandRaw({ ping: 1 });
33
+ isConnected = true;
34
+ stateName = 'connected';
35
+ } catch (error) {
36
+ stateName = 'disconnected';
37
+ }
38
+
39
+ return {
40
+ readyState: isConnected ? 1 : 0,
41
+ stateName,
42
+ isConnected,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * @param {number} maxTimeMS
48
+ * @returns {Promise<number>} Response time in milliseconds
49
+ */
50
+ async pingDatabase(maxTimeMS = 2000) {
51
+ const pingStart = Date.now();
52
+
53
+ const timeoutPromise = new Promise((_, reject) =>
54
+ setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
55
+ );
56
+
57
+ await Promise.race([
58
+ this.prisma.$runCommandRaw({ ping: 1 }),
59
+ timeoutPromise,
60
+ ]);
61
+
62
+ return Date.now() - pingStart;
63
+ }
64
+
65
+ async createCredential(credentialData) {
66
+ const now = new Date();
67
+ const document = {
68
+ ...credentialData,
69
+ createdAt: now,
70
+ updatedAt: now,
71
+ };
72
+
73
+ // Encrypt sensitive fields before insert
74
+ const encryptedDocument = await this.encryptionService.encryptFields(
75
+ 'Credential',
76
+ document
77
+ );
78
+ const insertedId = await insertOne(this.prisma, 'Credential', encryptedDocument);
79
+ const created = await findOne(this.prisma, 'Credential', { _id: insertedId });
80
+
81
+ // Decrypt after read
82
+ const decrypted = await this.encryptionService.decryptFields(
83
+ 'Credential',
84
+ created
85
+ );
86
+
87
+ return {
88
+ id: fromObjectId(decrypted._id),
89
+ ...decrypted,
90
+ };
91
+ }
92
+
93
+ async findCredentialById(id) {
94
+ const doc = await findOne(this.prisma, 'Credential', {
95
+ _id: toObjectId(id),
96
+ });
97
+
98
+ if (!doc) return null;
99
+
100
+ // Decrypt sensitive fields
101
+ const decrypted = await this.encryptionService.decryptFields('Credential', doc);
102
+
103
+ return {
104
+ id: fromObjectId(decrypted._id),
105
+ ...decrypted,
106
+ };
107
+ }
108
+
109
+ async getRawCredentialById(id) {
110
+ const objectId = toObjectId(id);
111
+ if (!objectId) return null;
112
+
113
+ const result = await this.prisma.$runCommandRaw({
114
+ find: 'Credential',
115
+ filter: { _id: objectId },
116
+ });
117
+
118
+ // Return raw document WITHOUT decryption
119
+ // This allows the test to verify that fields are actually encrypted in the database
120
+ return result?.cursor?.firstBatch?.[0] ?? null;
121
+ }
122
+
123
+ async deleteCredential(id) {
124
+ const objectId = toObjectId(id);
125
+ if (!objectId) return false;
126
+
127
+ const result = await deleteOne(this.prisma, 'Credential', { _id: objectId });
128
+ const deleted = result?.n ?? 0;
129
+ return deleted > 0;
130
+ }
131
+ }
132
+
133
+ module.exports = { HealthCheckRepositoryDocumentDB };
134
+
@@ -1,5 +1,6 @@
1
1
  const { HealthCheckRepositoryMongoDB } = require('./health-check-repository-mongodb');
2
2
  const { HealthCheckRepositoryPostgreSQL } = require('./health-check-repository-postgres');
3
+ const { HealthCheckRepositoryDocumentDB } = require('./health-check-repository-documentdb');
3
4
  const config = require('../config');
4
5
 
5
6
  /**
@@ -29,9 +30,12 @@ function createHealthCheckRepository({ prismaClient } = {}) {
29
30
  case 'postgresql':
30
31
  return new HealthCheckRepositoryPostgreSQL({ prismaClient });
31
32
 
33
+ case 'documentdb':
34
+ return new HealthCheckRepositoryDocumentDB({ prismaClient });
35
+
32
36
  default:
33
37
  throw new Error(
34
- `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
38
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
35
39
  );
36
40
  }
37
41
  }
@@ -40,4 +44,5 @@ module.exports = {
40
44
  createHealthCheckRepository,
41
45
  HealthCheckRepositoryMongoDB,
42
46
  HealthCheckRepositoryPostgreSQL,
47
+ HealthCheckRepositoryDocumentDB,
43
48
  };
@@ -15,72 +15,67 @@
15
15
  */
16
16
  class HealthCheckRepositoryInterface {
17
17
  /**
18
- * Ping database to verify connectivity
19
- *
20
- * @param {number} maxTimeMS - Maximum time in milliseconds
21
- * @returns {Promise<number>} Response time in milliseconds
18
+ * @returns {Promise<{readyState: number, stateName: string, isConnected: boolean}>}
22
19
  * @abstract
23
20
  */
24
- async pingDatabase(maxTimeMS) {
25
- throw new Error('Method pingDatabase must be implemented by subclass');
21
+ async getDatabaseConnectionState() {
22
+ throw new Error('Method getDatabaseConnectionState must be implemented by subclass');
26
23
  }
27
24
 
28
25
  /**
29
- * Save a test document
26
+ * Ping database to verify connectivity
30
27
  *
31
- * @param {Object} TestModel - Prisma model
32
- * @param {Object} data - Data to save
33
- * @returns {Promise<Object>} Saved document
28
+ * @param {number} maxTimeMS - Maximum time in milliseconds
29
+ * @returns {Promise<number>} Response time in milliseconds
34
30
  * @abstract
35
31
  */
36
- async saveTestDocument(TestModel, data) {
37
- throw new Error('Method saveTestDocument must be implemented by subclass');
32
+ async pingDatabase(maxTimeMS) {
33
+ throw new Error('Method pingDatabase must be implemented by subclass');
38
34
  }
39
35
 
40
36
  /**
41
- * Find test document by ID
37
+ * Persist an encrypted credential for health verification.
38
+ * Implementations should rely on Prisma so encryption middleware runs.
42
39
  *
43
- * @param {Object} TestModel - Prisma model
44
- * @param {string|number} id - Document ID
45
- * @returns {Promise<Object|null>} Document or null
40
+ * @param {Object} credentialData
41
+ * @returns {Promise<Object>} Persisted credential
46
42
  * @abstract
47
43
  */
48
- async findTestDocumentById(TestModel, id) {
49
- throw new Error('Method findTestDocumentById must be implemented by subclass');
44
+ async createCredential(credentialData) {
45
+ throw new Error('Method createCredential must be implemented by subclass');
50
46
  }
51
47
 
52
48
  /**
53
- * Get raw document from collection
49
+ * Retrieve credential by ID using Prisma (decrypted).
54
50
  *
55
- * @param {string} collectionName - Collection name
56
- * @param {Object} filter - Filter criteria
57
- * @returns {Promise<Object|null>} Raw document or null
51
+ * @param {string} id
52
+ * @returns {Promise<Object|null>}
58
53
  * @abstract
59
54
  */
60
- async getRawDocumentFromCollection(collectionName, filter) {
61
- throw new Error('Method getRawDocumentFromCollection must be implemented by subclass');
55
+ async findCredentialById(id) {
56
+ throw new Error('Method findCredentialById must be implemented by subclass');
62
57
  }
63
58
 
64
59
  /**
65
- * Delete test document
60
+ * Fetch raw credential document from the database (without decryption).
66
61
  *
67
- * @param {Object} TestModel - Prisma model
68
- * @param {string|number} id - Document ID
69
- * @returns {Promise<Object>} Deletion result
62
+ * @param {string} id
63
+ * @returns {Promise<Object|null>}
70
64
  * @abstract
71
65
  */
72
- async deleteTestDocument(TestModel, id) {
73
- throw new Error('Method deleteTestDocument must be implemented by subclass');
66
+ async getRawCredentialById(id) {
67
+ throw new Error('Method getRawCredentialById must be implemented by subclass');
74
68
  }
75
69
 
76
70
  /**
77
- * Get database connection state
71
+ * Delete credential by ID.
78
72
  *
79
- * @returns {Promise<Object>} Connection state info
73
+ * @param {string} id
74
+ * @returns {Promise<void>}
80
75
  * @abstract
81
76
  */
82
- async getDatabaseConnectionState() {
83
- throw new Error('Method getDatabaseConnectionState must be implemented by subclass');
77
+ async deleteCredential(id) {
78
+ throw new Error('Method deleteCredential must be implemented by subclass');
84
79
  }
85
80
  }
86
81
 
@@ -52,9 +52,7 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
52
52
  }),
53
53
  timeoutPromise
54
54
  ]);
55
-
56
- return Date.now() - pingStart;
57
- }
55
+
58
56
  return Date.now() - pingStart;
59
57
  }
60
58
 
@@ -32,7 +32,7 @@ class CheckDatabaseStateUseCase {
32
32
  /**
33
33
  * Execute check migration status
34
34
  *
35
- * @param {string} dbType - Database type (postgresql or mongodb)
35
+ * @param {string} dbType - Database type (postgresql, mongodb, or documentdb)
36
36
  * @param {string} stage - Deployment stage (default: 'production')
37
37
  * @returns {Promise<Object>} Migration status
38
38
  */
@@ -42,8 +42,8 @@ class CheckDatabaseStateUseCase {
42
42
  throw new ValidationError('dbType is required');
43
43
  }
44
44
 
45
- if (!['postgresql', 'mongodb'].includes(dbType)) {
46
- throw new ValidationError('dbType must be postgresql or mongodb');
45
+ if (!['postgresql', 'mongodb', 'documentdb'].includes(dbType)) {
46
+ throw new ValidationError('dbType must be postgresql, mongodb, or documentdb');
47
47
  }
48
48
 
49
49
  console.log(`Checking migration status for ${dbType} in ${stage}`);
@@ -26,7 +26,7 @@ class RunDatabaseMigrationUseCase {
26
26
  * Execute database migration
27
27
  *
28
28
  * @param {Object} params
29
- * @param {string} params.dbType - Database type ('postgresql' or 'mongodb')
29
+ * @param {string} params.dbType - Database type ('postgresql', 'mongodb', or 'documentdb')
30
30
  * @param {string} params.stage - Deployment stage (determines migration command)
31
31
  * @param {boolean} [params.verbose=false] - Enable verbose output
32
32
  * @returns {Promise<Object>} Migration result { success, dbType, stage, command, message }
@@ -61,19 +61,21 @@ class RunDatabaseMigrationUseCase {
61
61
  { dbType, stage, command: migrationCommand, step: 'migrate', output: migrationResult.output }
62
62
  );
63
63
  }
64
- } else if (dbType === 'mongodb') {
64
+ } else if (dbType === 'mongodb' || dbType === 'documentdb') {
65
65
  migrationCommand = 'db push';
66
66
  // Use non-interactive mode for automated/Lambda environments
67
67
  migrationResult = await this.prismaRunner.runPrismaDbPush(verbose, true);
68
68
 
69
69
  if (!migrationResult.success) {
70
70
  throw new MigrationError(
71
- `MongoDB push failed: ${migrationResult.error || 'Unknown error'}`,
71
+ `Mongo-compatible push failed: ${migrationResult.error || 'Unknown error'}`,
72
72
  { dbType, stage, command: migrationCommand, step: 'push', output: migrationResult.output }
73
73
  );
74
74
  }
75
75
  } else {
76
- throw new ValidationError(`Unsupported database type: ${dbType}. Must be 'postgresql' or 'mongodb'.`);
76
+ throw new ValidationError(
77
+ `Unsupported database type: ${dbType}. Must be 'postgresql', 'mongodb', or 'documentdb'.`
78
+ );
77
79
  }
78
80
 
79
81
  // Return success result
@@ -38,7 +38,7 @@ class TriggerDatabaseMigrationUseCase {
38
38
  *
39
39
  * @param {Object} params
40
40
  * @param {string} params.userId - User ID triggering the migration
41
- * @param {string} params.dbType - Database type ('postgresql' or 'mongodb')
41
+ * @param {string} params.dbType - Database type ('postgresql', 'mongodb', or 'documentdb')
42
42
  * @param {string} params.stage - Deployment stage (determines migration command)
43
43
  * @returns {Promise<Object>} Process info { success, processId, state, statusUrl, message }
44
44
  * @throws {ValidationError} If parameters are invalid
@@ -123,7 +123,7 @@ class TriggerDatabaseMigrationUseCase {
123
123
  throw new ValidationError('dbType must be a string');
124
124
  }
125
125
 
126
- const validDbTypes = ['postgresql', 'mongodb'];
126
+ const validDbTypes = ['postgresql', 'mongodb', 'documentdb'];
127
127
  if (!validDbTypes.includes(dbType)) {
128
128
  throw new ValidationError(
129
129
  `Invalid dbType: "${dbType}". Must be one of: ${validDbTypes.join(', ')}`
@@ -47,9 +47,9 @@ const config = require('../config');
47
47
  * ```
48
48
  */
49
49
  async function initializeMongoDBSchema() {
50
- // Only run for MongoDB
51
- if (config.DB_TYPE !== 'mongodb') {
52
- console.log('Schema initialization skipped - not using MongoDB');
50
+ // Only run for MongoDB-compatible databases
51
+ if (config.DB_TYPE !== 'mongodb' && config.DB_TYPE !== 'documentdb') {
52
+ console.log('Schema initialization skipped - not using MongoDB-compatible database');
53
53
  return;
54
54
  }
55
55
 
@@ -61,7 +61,7 @@ async function initializeMongoDBSchema() {
61
61
  );
62
62
  }
63
63
 
64
- console.log('Initializing MongoDB schema - ensuring all collections exist...');
64
+ console.log('Initializing MongoDB-compatible schema - ensuring all collections exist...');
65
65
  const startTime = Date.now();
66
66
 
67
67
  try {
@@ -77,7 +77,7 @@ async function initializeMongoDBSchema() {
77
77
 
78
78
  const duration = Date.now() - startTime;
79
79
  console.log(
80
- `MongoDB schema initialization complete - ${collections.length} collections verified (${duration}ms)`
80
+ `MongoDB-compatible schema initialization complete - ${collections.length} collections verified (${duration}ms)`
81
81
  );
82
82
  } catch (error) {
83
83
  console.error('Failed to initialize MongoDB schema:', error.message);
@@ -10,12 +10,17 @@ const chalk = require('chalk');
10
10
 
11
11
  /**
12
12
  * Gets the path to the Prisma schema file for the database type
13
- * @param {'mongodb'|'postgresql'} dbType - Database type
13
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
14
14
  * @param {string} projectRoot - Project root directory
15
15
  * @returns {string} Absolute path to schema file
16
16
  * @throws {Error} If schema file doesn't exist
17
17
  */
18
+ function normalizeMongoCompatible(dbType) {
19
+ return dbType === 'documentdb' ? 'mongodb' : dbType;
20
+ }
21
+
18
22
  function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
23
+ const normalizedType = normalizeMongoCompatible(dbType);
19
24
  // Try multiple locations for the schema file
20
25
  // Priority order:
21
26
  // 1. Lambda layer path (where the schema actually exists in deployed Lambda)
@@ -23,10 +28,10 @@ function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
23
28
  // 3. Parent node_modules (workspace/monorepo setup)
24
29
  const possiblePaths = [
25
30
  // Lambda layer path - this is where the schema actually exists in deployed Lambda
26
- `/opt/nodejs/node_modules/generated/prisma-${dbType}/schema.prisma`,
31
+ `/opt/nodejs/node_modules/generated/prisma-${normalizedType}/schema.prisma`,
27
32
  // Check where Frigg is installed via npm (production scenario)
28
- path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma'),
29
- path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${dbType}`, 'schema.prisma')
33
+ path.join(projectRoot, 'node_modules', '@friggframework', 'core', `prisma-${normalizedType}`, 'schema.prisma'),
34
+ path.join(projectRoot, '..', 'node_modules', '@friggframework', 'core', `prisma-${normalizedType}`, 'schema.prisma')
30
35
  ];
31
36
 
32
37
  for (const schemaPath of possiblePaths) {
@@ -44,7 +49,7 @@ function getPrismaSchemaPath(dbType, projectRoot = process.cwd()) {
44
49
 
45
50
  /**
46
51
  * Runs prisma generate for the specified database type
47
- * @param {'mongodb'|'postgresql'} dbType - Database type
52
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
48
53
  * @param {boolean} verbose - Enable verbose output
49
54
  * @returns {Promise<Object>} { success: boolean, output?: string, error?: string }
50
55
  */
@@ -53,18 +58,19 @@ async function runPrismaGenerate(dbType, verbose = false) {
53
58
  const schemaPath = getPrismaSchemaPath(dbType);
54
59
 
55
60
  // Check if Prisma client already exists (e.g., in Lambda or pre-generated)
56
- const generatedClientPath = path.join(path.dirname(path.dirname(schemaPath)), 'generated', `prisma-${dbType}`, 'client.js');
61
+ const normalizedType = normalizeMongoCompatible(dbType);
62
+ const generatedClientPath = path.join(path.dirname(path.dirname(schemaPath)), 'generated', `prisma-${normalizedType}`, 'client.js');
57
63
  const isLambdaEnvironment = !!process.env.AWS_LAMBDA_FUNCTION_NAME || !!process.env.LAMBDA_TASK_ROOT;
58
64
 
59
65
  // In Lambda, also check the layer path (/opt/nodejs/node_modules)
60
- const lambdaLayerClientPath = `/opt/nodejs/node_modules/generated/prisma-${dbType}/client.js`;
66
+ const lambdaLayerClientPath = `/opt/nodejs/node_modules/generated/prisma-${normalizedType}/client.js`;
61
67
 
62
68
  const clientExists = fs.existsSync(generatedClientPath) || (isLambdaEnvironment && fs.existsSync(lambdaLayerClientPath));
63
69
 
64
70
  if (clientExists) {
65
71
  const foundPath = fs.existsSync(generatedClientPath) ? generatedClientPath : lambdaLayerClientPath;
66
72
  if (verbose) {
67
- console.log(chalk.gray(`✓ Prisma client already generated at: ${foundPath}`));
73
+ console.log(chalk.gray(`✓ Prisma client already generated at: ${foundPath}`));
68
74
  }
69
75
  if (isLambdaEnvironment) {
70
76
  if (verbose) {
@@ -110,7 +116,7 @@ async function runPrismaGenerate(dbType, verbose = false) {
110
116
 
111
117
  /**
112
118
  * Checks database migration status
113
- * @param {'mongodb'|'postgresql'} dbType - Database type
119
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
114
120
  * @returns {Promise<Object>} { upToDate: boolean, pendingMigrations?: number, error?: string }
115
121
  */
116
122
  async function checkDatabaseState(dbType) {