@friggframework/devtools 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 (32) hide show
  1. package/frigg-cli/README.md +13 -14
  2. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +267 -166
  3. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +45 -14
  4. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +44 -3
  5. package/frigg-cli/db-setup-command/index.js +75 -22
  6. package/frigg-cli/deploy-command/index.js +6 -3
  7. package/frigg-cli/utils/database-validator.js +18 -5
  8. package/frigg-cli/utils/error-messages.js +84 -12
  9. package/infrastructure/README.md +28 -0
  10. package/infrastructure/domains/database/migration-builder.js +26 -20
  11. package/infrastructure/domains/database/migration-builder.test.js +27 -0
  12. package/infrastructure/domains/integration/integration-builder.js +17 -13
  13. package/infrastructure/domains/integration/integration-builder.test.js +23 -0
  14. package/infrastructure/domains/networking/vpc-builder.js +240 -18
  15. package/infrastructure/domains/networking/vpc-builder.test.js +711 -13
  16. package/infrastructure/domains/networking/vpc-resolver.js +221 -40
  17. package/infrastructure/domains/networking/vpc-resolver.test.js +318 -18
  18. package/infrastructure/domains/security/kms-builder.js +55 -6
  19. package/infrastructure/domains/security/kms-builder.test.js +19 -1
  20. package/infrastructure/domains/shared/cloudformation-discovery.js +310 -13
  21. package/infrastructure/domains/shared/cloudformation-discovery.test.js +395 -0
  22. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +41 -6
  23. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +39 -0
  24. package/infrastructure/domains/shared/resource-discovery.js +17 -5
  25. package/infrastructure/domains/shared/resource-discovery.test.js +36 -0
  26. package/infrastructure/domains/shared/utilities/base-definition-factory.js +30 -20
  27. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +43 -0
  28. package/infrastructure/infrastructure-composer.js +11 -3
  29. package/infrastructure/scripts/build-prisma-layer.js +153 -78
  30. package/infrastructure/scripts/build-prisma-layer.test.js +27 -11
  31. package/layers/prisma/.build-complete +3 -0
  32. package/package.json +7 -7
@@ -113,6 +113,15 @@ describe('Database Validator Utility', () => {
113
113
  expect(result.error).toBeUndefined();
114
114
  });
115
115
 
116
+ it('should return documentdb when core returns documentdb', () => {
117
+ getDatabaseTypeFromCore.mockReturnValue('documentdb');
118
+
119
+ const result = getDatabaseType();
120
+
121
+ expect(result.dbType).toBe('documentdb');
122
+ expect(result.error).toBeUndefined();
123
+ });
124
+
116
125
  it('should return error when core throws error', () => {
117
126
  getDatabaseTypeFromCore.mockImplementation(() => {
118
127
  throw new Error('[Frigg] Database not configured');
@@ -177,6 +186,17 @@ describe('Database Validator Utility', () => {
177
186
  expect(disconnectPrisma).toHaveBeenCalled();
178
187
  });
179
188
 
189
+ it('should connect successfully to DocumentDB and use $runCommandRaw', async () => {
190
+ const result = await testDatabaseConnection('mongodb://localhost', 'documentdb');
191
+
192
+ expect(result.connected).toBe(true);
193
+ expect(result.error).toBeUndefined();
194
+ expect(connectPrisma).toHaveBeenCalled();
195
+ expect(mockClient.$runCommandRaw).toHaveBeenCalledWith({ ping: 1 });
196
+ expect(mockClient.$queryRaw).not.toHaveBeenCalled(); // DocumentDB doesn't use SQL
197
+ expect(disconnectPrisma).toHaveBeenCalled();
198
+ });
199
+
180
200
  it('should connect successfully to PostgreSQL and use $queryRaw', async () => {
181
201
  const result = await testDatabaseConnection('postgresql://localhost', 'postgresql');
182
202
 
@@ -303,20 +323,32 @@ describe('Database Validator Utility', () => {
303
323
  // Note: Testing require.resolve behavior requires integration tests with real packages
304
324
  // These unit tests focus on error handling and package name selection
305
325
 
306
- it('should use correct package name for MongoDB', () => {
307
- // When MongoDB client doesn't exist, error message reveals the package name used
326
+ it('should check for MongoDB client directory', () => {
327
+ // When path doesn't exist, returns error about resolving module
308
328
  const result = checkPrismaClientGenerated('mongodb', '/nonexistent/path');
309
329
 
310
330
  expect(result.generated).toBe(false);
311
- expect(result.error).toContain('@prisma-mongodb/client');
331
+ expect(result.error).toBeDefined();
332
+ expect(result.error).toContain('Failed to check Prisma client');
312
333
  });
313
334
 
314
- it('should use correct package name for PostgreSQL', () => {
315
- // When PostgreSQL client doesn't exist, error message reveals the package name used
335
+ it('should check for PostgreSQL client directory', () => {
336
+ // When path doesn't exist, returns error about resolving module
316
337
  const result = checkPrismaClientGenerated('postgresql', '/nonexistent/path');
317
338
 
318
339
  expect(result.generated).toBe(false);
319
- expect(result.error).toContain('@prisma-postgresql/client');
340
+ expect(result.error).toBeDefined();
341
+ expect(result.error).toContain('Failed to check Prisma client');
342
+ });
343
+
344
+ it('should normalize DocumentDB to check MongoDB client directory', () => {
345
+ // DocumentDB should use the same client as MongoDB (prisma-mongodb directory)
346
+ // When path doesn't exist, returns error about resolving module
347
+ const result = checkPrismaClientGenerated('documentdb', '/nonexistent/path');
348
+
349
+ expect(result.generated).toBe(false);
350
+ expect(result.error).toBeDefined();
351
+ expect(result.error).toContain('Failed to check Prisma client');
320
352
  });
321
353
 
322
354
  it('should return error when MongoDB client not found', () => {
@@ -324,9 +356,8 @@ describe('Database Validator Utility', () => {
324
356
 
325
357
  expect(result.generated).toBe(false);
326
358
  expect(result.error).toBeDefined();
327
- expect(result.error).toContain('not found');
328
- expect(result.error).toContain('@prisma-mongodb/client');
329
- expect(result.error).toContain('frigg db:setup');
359
+ // Error will be about resolving @friggframework/core module
360
+ expect(result.error).toContain('Failed to check Prisma client');
330
361
  });
331
362
 
332
363
  it('should return error when PostgreSQL client not found', () => {
@@ -334,15 +365,15 @@ describe('Database Validator Utility', () => {
334
365
 
335
366
  expect(result.generated).toBe(false);
336
367
  expect(result.error).toBeDefined();
337
- expect(result.error).toContain('not found');
338
- expect(result.error).toContain('@prisma-postgresql/client');
339
- expect(result.error).toContain('frigg db:setup');
368
+ // Error will be about resolving @friggframework/core module
369
+ expect(result.error).toContain('Failed to check Prisma client');
340
370
  });
341
371
 
342
- it('should provide helpful error message suggesting db:setup command', () => {
372
+ it('should return error when core package cannot be resolved', () => {
343
373
  const result = checkPrismaClientGenerated('mongodb', '/nonexistent/path');
344
374
 
345
- expect(result.error).toContain('frigg db:setup');
375
+ expect(result.generated).toBe(false);
376
+ expect(result.error).toContain('Failed to check Prisma client');
346
377
  });
347
378
 
348
379
  it('should use process.cwd() by default when no project root specified', () => {
@@ -16,6 +16,12 @@ describe('Error Messages Utility', () => {
16
16
  expect(DATABASE_URL_EXAMPLES.mongodb).toContain('replicaSet=rs0');
17
17
  });
18
18
 
19
+ it('should include DocumentDB connection string example', () => {
20
+ expect(DATABASE_URL_EXAMPLES.documentdb).toBeDefined();
21
+ expect(DATABASE_URL_EXAMPLES.documentdb).toContain('docdb');
22
+ expect(DATABASE_URL_EXAMPLES.documentdb).toContain('retryWrites=false');
23
+ });
24
+
19
25
  it('should include PostgreSQL connection string example', () => {
20
26
  expect(DATABASE_URL_EXAMPLES.postgresql).toBeDefined();
21
27
  expect(DATABASE_URL_EXAMPLES.postgresql).toContain('postgresql://');
@@ -31,10 +37,11 @@ describe('Error Messages Utility', () => {
31
37
  expect(typeof message).toBe('string');
32
38
  });
33
39
 
34
- it('should include both database type examples', () => {
40
+ it('should include all database type examples', () => {
35
41
  const message = getDatabaseUrlMissingError();
36
42
 
37
43
  expect(message).toContain('MongoDB');
44
+ expect(message).toContain('DocumentDB');
38
45
  expect(message).toContain('PostgreSQL');
39
46
  });
40
47
 
@@ -42,6 +49,7 @@ describe('Error Messages Utility', () => {
42
49
  const message = getDatabaseUrlMissingError();
43
50
 
44
51
  expect(message).toContain(DATABASE_URL_EXAMPLES.mongodb);
52
+ expect(message).toContain(DATABASE_URL_EXAMPLES.documentdb);
45
53
  expect(message).toContain(DATABASE_URL_EXAMPLES.postgresql);
46
54
  });
47
55
 
@@ -119,6 +127,14 @@ describe('Error Messages Utility', () => {
119
127
  expect(message).toContain('27017');
120
128
  });
121
129
 
130
+ it('should include DocumentDB-specific troubleshooting for DocumentDB', () => {
131
+ const message = getDatabaseConnectionError(mockError, 'documentdb');
132
+
133
+ expect(message).toContain('DocumentDB');
134
+ expect(message).toContain('retryWrites=false');
135
+ expect(message).toContain('global-bundle.pem');
136
+ });
137
+
122
138
  it('should include PostgreSQL-specific troubleshooting for PostgreSQL', () => {
123
139
  const message = getDatabaseConnectionError(mockError, 'postgresql');
124
140
 
@@ -150,6 +166,12 @@ describe('Error Messages Utility', () => {
150
166
  expect(messagePostgres).toContain('Troubleshooting');
151
167
  });
152
168
 
169
+ it('should display database name in the error output', () => {
170
+ const message = getDatabaseConnectionError(mockError, 'documentdb');
171
+
172
+ expect(message).toContain('AWS DocumentDB (MongoDB-compatible)');
173
+ });
174
+
153
175
  it('should show DATABASE_URL when available', () => {
154
176
  process.env.DATABASE_URL = 'mongodb://test';
155
177
  const message = getDatabaseConnectionError(mockError, 'mongodb');
@@ -187,6 +209,13 @@ describe('Error Messages Utility', () => {
187
209
  expect(message).toContain('@prisma-mongodb/client');
188
210
  });
189
211
 
212
+ it('should mention Mongo client reuse for DocumentDB', () => {
213
+ const message = getPrismaClientNotGeneratedError('documentdb');
214
+
215
+ expect(message).toContain('@prisma-mongodb/client');
216
+ expect(message).toContain('DocumentDB reuses the MongoDB Prisma client');
217
+ });
218
+
190
219
  it('should include correct client package name for PostgreSQL', () => {
191
220
  const message = getPrismaClientNotGeneratedError('postgresql');
192
221
 
@@ -255,13 +284,19 @@ describe('Error Messages Utility', () => {
255
284
  it('should include database type for MongoDB', () => {
256
285
  const message = getDatabaseSetupSuccess('mongodb', 'development');
257
286
 
258
- expect(message).toContain('mongodb');
287
+ expect(message).toContain('MongoDB');
259
288
  });
260
289
 
261
290
  it('should include database type for PostgreSQL', () => {
262
291
  const message = getDatabaseSetupSuccess('postgresql', 'production');
263
292
 
264
- expect(message).toContain('postgresql');
293
+ expect(message).toContain('PostgreSQL');
294
+ });
295
+
296
+ it('should include database type for DocumentDB', () => {
297
+ const message = getDatabaseSetupSuccess('documentdb', 'production');
298
+
299
+ expect(message).toContain('AWS DocumentDB (MongoDB-compatible)');
265
300
  });
266
301
 
267
302
  it('should include stage information', () => {
@@ -280,6 +315,12 @@ describe('Error Messages Utility', () => {
280
315
  expect(postgresMessage).toContain('Migrations applied');
281
316
  });
282
317
 
318
+ it('should mention DocumentDB-specific schema messaging', () => {
319
+ const message = getDatabaseSetupSuccess('documentdb', 'development');
320
+
321
+ expect(message).toContain('Schema pushed to DocumentDB');
322
+ });
323
+
283
324
  it('should suggest next steps', () => {
284
325
  const message = getDatabaseSetupSuccess('mongodb', 'development');
285
326
 
@@ -4,22 +4,39 @@ const dotenv = require('dotenv');
4
4
  const {
5
5
  validateDatabaseUrl,
6
6
  getDatabaseType,
7
- checkPrismaClientGenerated
7
+ checkPrismaClientGenerated,
8
8
  } = require('../utils/database-validator');
9
9
  const {
10
10
  runPrismaGenerate,
11
11
  checkDatabaseState,
12
12
  runPrismaMigrate,
13
13
  runPrismaDbPush,
14
- getMigrationCommand
14
+ getMigrationCommand,
15
15
  } = require('@friggframework/core/database/utils/prisma-runner');
16
16
  const {
17
17
  getDatabaseUrlMissingError,
18
18
  getDatabaseTypeNotConfiguredError,
19
19
  getPrismaCommandError,
20
- getDatabaseSetupSuccess
20
+ getDatabaseSetupSuccess,
21
21
  } = require('../utils/error-messages');
22
22
 
23
+ function getDatabaseDisplayName(dbType) {
24
+ switch (dbType) {
25
+ case 'postgresql':
26
+ return 'PostgreSQL';
27
+ case 'mongodb':
28
+ return 'MongoDB';
29
+ case 'documentdb':
30
+ return 'AWS DocumentDB (MongoDB-compatible)';
31
+ default:
32
+ return dbType;
33
+ }
34
+ }
35
+
36
+ function getPrismaRunnerDbType(dbType) {
37
+ return dbType === 'documentdb' ? 'mongodb' : dbType;
38
+ }
39
+
23
40
  /**
24
41
  * Database Setup Command
25
42
  * Sets up the database for a Frigg application:
@@ -75,10 +92,16 @@ async function dbSetupCommand(options = {}) {
75
92
  }
76
93
 
77
94
  const dbType = dbTypeResult.dbType;
78
- console.log(chalk.cyan(`Database type: ${dbType}`));
95
+ const dbDisplayName = getDatabaseDisplayName(dbType);
96
+ const isPostgres = dbType === 'postgresql';
97
+ const isDocumentDb = dbType === 'documentdb';
98
+ const isMongoFamily = dbType === 'mongodb' || isDocumentDb;
99
+ const runnerDbType = getPrismaRunnerDbType(dbType);
100
+
101
+ console.log(chalk.cyan(`Database type: ${dbDisplayName}`));
79
102
 
80
103
  if (verbose) {
81
- console.log(chalk.green(`✓ Using ${dbType}\n`));
104
+ console.log(chalk.green(`✓ Using ${dbDisplayName}\n`));
82
105
  }
83
106
 
84
107
  // Step 3: Check if Prisma client exists, generate if needed
@@ -91,22 +114,35 @@ async function dbSetupCommand(options = {}) {
91
114
 
92
115
  if (clientCheck.generated && !forceRegenerate) {
93
116
  // Client already exists and --force not specified
94
- console.log(chalk.green('✓ Prisma client already exists (skipping generation)\n'));
117
+ console.log(
118
+ chalk.green(
119
+ '✓ Prisma client already exists (skipping generation)\n'
120
+ )
121
+ );
95
122
  if (verbose) {
96
- console.log(chalk.gray(` Client location: ${clientCheck.path}\n`));
123
+ console.log(
124
+ chalk.gray(` Client location: ${clientCheck.path}\n`)
125
+ );
97
126
  }
98
127
  } else {
99
128
  // Client doesn't exist OR --force specified - generate it
100
129
  if (forceRegenerate && clientCheck.generated) {
101
- console.log(chalk.yellow('⚠️ Forcing Prisma client regeneration...'));
130
+ console.log(
131
+ chalk.yellow('⚠️ Forcing Prisma client regeneration...')
132
+ );
102
133
  } else {
103
134
  console.log(chalk.cyan('Generating Prisma client...'));
104
135
  }
105
136
 
106
- const generateResult = await runPrismaGenerate(dbType, verbose);
137
+ const generateResult = await runPrismaGenerate(
138
+ runnerDbType,
139
+ verbose
140
+ );
107
141
 
108
142
  if (!generateResult.success) {
109
- console.error(getPrismaCommandError('generate', generateResult.error));
143
+ console.error(
144
+ getPrismaCommandError('generate', generateResult.error)
145
+ );
110
146
  if (generateResult.output) {
111
147
  console.error(chalk.gray(generateResult.output));
112
148
  }
@@ -124,25 +160,32 @@ async function dbSetupCommand(options = {}) {
124
160
  console.log(chalk.gray('Step 4: Checking database state...'));
125
161
  }
126
162
 
127
- const stateCheck = await checkDatabaseState(dbType);
163
+ const stateCheck = await checkDatabaseState(runnerDbType);
128
164
 
129
165
  // Step 5: Run migrations or db push
130
- if (dbType === 'postgresql') {
166
+ if (isPostgres) {
131
167
  console.log(chalk.cyan('Running database migrations...'));
132
168
 
133
169
  const migrationCommand = getMigrationCommand(stage);
134
170
 
135
171
  if (verbose) {
136
- console.log(chalk.gray(`Using migration command: ${migrationCommand}`));
172
+ console.log(
173
+ chalk.gray(`Using migration command: ${migrationCommand}`)
174
+ );
137
175
  }
138
176
 
139
177
  if (stateCheck.upToDate && migrationCommand === 'deploy') {
140
178
  console.log(chalk.yellow('Database is already up-to-date'));
141
179
  } else {
142
- const migrateResult = await runPrismaMigrate(migrationCommand, verbose);
180
+ const migrateResult = await runPrismaMigrate(
181
+ migrationCommand,
182
+ verbose
183
+ );
143
184
 
144
185
  if (!migrateResult.success) {
145
- console.error(getPrismaCommandError('migrate', migrateResult.error));
186
+ console.error(
187
+ getPrismaCommandError('migrate', migrateResult.error)
188
+ );
146
189
  if (migrateResult.output) {
147
190
  console.error(chalk.gray(migrateResult.output));
148
191
  }
@@ -151,14 +194,19 @@ async function dbSetupCommand(options = {}) {
151
194
 
152
195
  console.log(chalk.green('✓ Migrations applied\n'));
153
196
  }
197
+ } else if (isMongoFamily) {
198
+ const targetName = isDocumentDb
199
+ ? 'AWS DocumentDB (MongoDB-compatible)'
200
+ : 'MongoDB';
154
201
 
155
- } else if (dbType === 'mongodb') {
156
- console.log(chalk.cyan('Pushing schema to MongoDB...'));
202
+ console.log(chalk.cyan(`Pushing schema to ${targetName}...`));
157
203
 
158
204
  const pushResult = await runPrismaDbPush(verbose);
159
205
 
160
206
  if (!pushResult.success) {
161
- console.error(getPrismaCommandError('db push', pushResult.error));
207
+ console.error(
208
+ getPrismaCommandError('db push', pushResult.error)
209
+ );
162
210
  if (pushResult.output) {
163
211
  console.error(chalk.gray(pushResult.output));
164
212
  }
@@ -170,7 +218,6 @@ async function dbSetupCommand(options = {}) {
170
218
 
171
219
  // Success!
172
220
  console.log(getDatabaseSetupSuccess(dbType, stage));
173
-
174
221
  } catch (error) {
175
222
  console.error(chalk.red('\n❌ Database setup failed'));
176
223
  console.error(chalk.gray(error.message));
@@ -182,9 +229,15 @@ async function dbSetupCommand(options = {}) {
182
229
 
183
230
  console.error(chalk.yellow('\nTroubleshooting:'));
184
231
  console.error(chalk.gray(' • Verify DATABASE_URL in your .env file'));
185
- console.error(chalk.gray(' • Check database is running and accessible'));
186
- console.error(chalk.gray(' • Ensure app definition has database configuration'));
187
- console.error(chalk.gray(' • Run with --verbose flag for more details'));
232
+ console.error(
233
+ chalk.gray(' • Check database is running and accessible')
234
+ );
235
+ console.error(
236
+ chalk.gray(' • Ensure app definition has database configuration')
237
+ );
238
+ console.error(
239
+ chalk.gray(' • Run with --verbose flag for more details')
240
+ );
188
241
 
189
242
  process.exit(1);
190
243
  }
@@ -284,8 +284,10 @@ async function deployCommand(options) {
284
284
 
285
285
  console.log('\n✓ Deployment completed successfully!');
286
286
 
287
- // Run post-deployment health check (unless --skip-doctor)
288
- if (!options.skipDoctor) {
287
+ const skipHealthCheck = options.skipDoctor || appDefinition?.deployment?.skipPostDeploymentHealthCheck;
288
+
289
+ // Run post-deployment health check (unless disabled)
290
+ if (!skipHealthCheck) {
289
291
  const stackName = getStackName(appDefinition, options);
290
292
 
291
293
  if (stackName) {
@@ -295,7 +297,8 @@ async function deployCommand(options) {
295
297
  console.log(' Run "frigg doctor <stack-name>" manually to check stack health');
296
298
  }
297
299
  } else {
298
- console.log('\n⏭️ Skipping post-deployment health check (--skip-doctor)');
300
+ const reason = options.skipDoctor ? '--skip-doctor flag' : 'deployment.skipPostDeploymentHealthCheck: true';
301
+ console.log(`\n⏭️ Skipping post-deployment health check (${reason})`);
299
302
  }
300
303
  }
301
304
 
@@ -8,6 +8,16 @@ const { connectPrisma, disconnectPrisma } = require('@friggframework/core/databa
8
8
  * Validates database configuration and connectivity for Frigg applications
9
9
  */
10
10
 
11
+ /**
12
+ * Normalizes MongoDB-compatible database types to 'mongodb'
13
+ * DocumentDB uses the same Prisma client as MongoDB
14
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
15
+ * @returns {'mongodb'|'postgresql'} Normalized database type
16
+ */
17
+ function normalizeMongoCompatible(dbType) {
18
+ return dbType === 'documentdb' ? 'mongodb' : dbType;
19
+ }
20
+
11
21
  /**
12
22
  * Validates that DATABASE_URL environment variable exists and has a value
13
23
  * @returns {Object} { valid: boolean, url?: string, error?: string }
@@ -62,7 +72,7 @@ function getDatabaseType() {
62
72
  * Uses the same Prisma client configuration as runtime
63
73
  *
64
74
  * @param {string} databaseUrl - Database connection URL (for validation purposes)
65
- * @param {'mongodb'|'postgresql'} dbType - Database type to determine appropriate health check
75
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type to determine appropriate health check
66
76
  * @param {number} timeout - Connection timeout in milliseconds (default: 5000)
67
77
  * @returns {Promise<Object>} { connected: boolean, error?: string }
68
78
  */
@@ -78,8 +88,8 @@ async function testDatabaseConnection(databaseUrl, dbType, timeout = 5000) {
78
88
  const client = await Promise.race([connectPromise, timeoutPromise]);
79
89
 
80
90
  // Test with database-appropriate health check
81
- // MongoDB doesn't support SQL, so we use the native ping command
82
- if (dbType === 'mongodb') {
91
+ // MongoDB and DocumentDB don't support SQL, so we use the native ping command
92
+ if (dbType === 'mongodb' || dbType === 'documentdb') {
83
93
  // Use MongoDB's native ping command via $runCommandRaw
84
94
  await client.$runCommandRaw({ ping: 1 });
85
95
  } else {
@@ -109,12 +119,15 @@ async function testDatabaseConnection(databaseUrl, dbType, timeout = 5000) {
109
119
  * Checks if Prisma client is generated for the database type
110
120
  * Checks for the generated client directory in @friggframework/core/generated
111
121
  *
112
- * @param {'mongodb'|'postgresql'} dbType - Database type
122
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
113
123
  * @param {string} projectRoot - Project root directory (used for require.resolve context)
114
124
  * @returns {Object} { generated: boolean, path?: string, error?: string }
115
125
  */
116
126
  function checkPrismaClientGenerated(dbType, projectRoot = process.cwd()) {
117
127
  try {
128
+ // Normalize DocumentDB to MongoDB (they use the same Prisma client)
129
+ const normalizedType = normalizeMongoCompatible(dbType);
130
+
118
131
  // Resolve where @friggframework/core actually is
119
132
  // This handles file: dependencies and symlinks correctly
120
133
  const corePackagePath = require.resolve('@friggframework/core', {
@@ -123,7 +136,7 @@ function checkPrismaClientGenerated(dbType, projectRoot = process.cwd()) {
123
136
  const corePackageDir = path.dirname(corePackagePath);
124
137
 
125
138
  // Check for the generated client directory (same path core uses)
126
- const clientPath = path.join(corePackageDir, 'generated', `prisma-${dbType}`);
139
+ const clientPath = path.join(corePackageDir, 'generated', `prisma-${normalizedType}`);
127
140
  const clientIndexPath = path.join(clientPath, 'index.js');
128
141
 
129
142
  if (fs.existsSync(clientIndexPath)) {
@@ -6,13 +6,37 @@ const chalk = require('chalk');
6
6
  */
7
7
 
8
8
  /**
9
- * Database URL examples for both supported database types
9
+ * Normalizes MongoDB-compatible database types to 'mongodb'
10
+ * DocumentDB uses the same Prisma client as MongoDB
11
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
12
+ * @returns {'mongodb'|'postgresql'} Normalized database type
13
+ */
14
+ function normalizeMongoCompatible(dbType) {
15
+ return dbType === 'documentdb' ? 'mongodb' : dbType;
16
+ }
17
+
18
+ /**
19
+ * Database URL examples for supported database types
10
20
  */
11
21
  const DATABASE_URL_EXAMPLES = {
12
- mongodb: 'mongodb://localhost:27017/frigg?replicaSet=rs0',
13
- postgresql: 'postgresql://postgres:postgres@localhost:5432/frigg?schema=public'
22
+ mongodb: 'mongodb://localhost:27017/frigg?replicaSet=rs0',
23
+ documentdb: 'mongodb://frigg-user:yourPassword@docdb-cluster.cluster-xyz123.us-east-1.docdb.amazonaws.com:27017/frigg?tls=true&replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false',
24
+ postgresql: 'postgresql://postgres:postgres@localhost:5432/frigg?schema=public'
14
25
  };
15
26
 
27
+ function getDatabaseDisplayName(dbType) {
28
+ switch (dbType) {
29
+ case 'mongodb':
30
+ return 'MongoDB';
31
+ case 'documentdb':
32
+ return 'AWS DocumentDB (MongoDB-compatible)';
33
+ case 'postgresql':
34
+ return 'PostgreSQL';
35
+ default:
36
+ return dbType;
37
+ }
38
+ }
39
+
16
40
  /**
17
41
  * Gets helpful error message for missing DATABASE_URL
18
42
  * @returns {string} Formatted error message
@@ -26,6 +50,9 @@ ${chalk.bold('Add DATABASE_URL to your .env file:')}
26
50
  ${chalk.cyan('For MongoDB:')}
27
51
  ${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.mongodb}"`)}
28
52
 
53
+ ${chalk.cyan('For AWS DocumentDB (MongoDB-compatible):')}
54
+ ${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.documentdb}"`)}
55
+
29
56
  ${chalk.cyan('For PostgreSQL:')}
30
57
  ${chalk.gray('DATABASE_URL')}=${chalk.green(`"${DATABASE_URL_EXAMPLES.postgresql}"`)}
31
58
 
@@ -73,19 +100,26 @@ const appDefinition = {
73
100
  }
74
101
  };
75
102
  `)}
103
+
104
+ ${chalk.gray('DocumentDB uses the MongoDB Prisma client. Make sure TLS is enabled and replica set compatibility is configured on your cluster.')}
76
105
  `;
77
106
  }
78
107
 
79
108
  /**
80
109
  * Gets helpful error message for database connection failure
81
110
  * @param {string} error - Connection error message
82
- * @param {'mongodb'|'postgresql'} dbType - Database type
111
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
83
112
  * @returns {string} Formatted error message
84
113
  */
85
114
  function getDatabaseConnectionError(error, dbType) {
86
- const troubleshootingSteps = dbType === 'mongodb'
87
- ? getMongoDatabaseTroubleshooting()
88
- : getPostgresTroubleshooting();
115
+ let troubleshootingSteps;
116
+ if (dbType === 'documentdb') {
117
+ troubleshootingSteps = getDocumentDbTroubleshooting();
118
+ } else if (dbType === 'mongodb') {
119
+ troubleshootingSteps = getMongoDatabaseTroubleshooting();
120
+ } else {
121
+ troubleshootingSteps = getPostgresTroubleshooting();
122
+ }
89
123
 
90
124
  return `
91
125
  ${chalk.red('❌ Failed to connect to database')}
@@ -93,6 +127,8 @@ ${chalk.red('❌ Failed to connect to database')}
93
127
  ${chalk.bold('Connection error:')}
94
128
  ${chalk.gray(error)}
95
129
 
130
+ ${chalk.bold('Database:')} ${chalk.cyan(getDatabaseDisplayName(dbType))}
131
+
96
132
  ${chalk.bold('Troubleshooting steps:')}
97
133
  ${troubleshootingSteps}
98
134
 
@@ -128,6 +164,26 @@ ${chalk.gray('5.')} Check network/firewall settings
128
164
  `;
129
165
  }
130
166
 
167
+ function getDocumentDbTroubleshooting() {
168
+ return `
169
+ ${chalk.gray('1.')} Verify DocumentDB cluster is available
170
+ ${chalk.cyan('aws docdb describe-db-clusters --db-cluster-identifier <name>')}
171
+
172
+ ${chalk.gray('2.')} Use TLS with the AWS CA bundle
173
+ ${chalk.gray('Download: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem')}
174
+ ${chalk.gray('Add ?tls=true&tlsCAFile=/path/to/global-bundle.pem to DATABASE_URL')}
175
+
176
+ ${chalk.gray('3.')} Disable retryable writes (DocumentDB limitation)
177
+ ${chalk.gray('Add retryWrites=false to DATABASE_URL query string')}
178
+
179
+ ${chalk.gray('4.')} Include replicaSet parameter
180
+ ${chalk.gray('DocumentDB requires replicaSet=rs0 even for single-node clusters')}
181
+
182
+ ${chalk.gray('5.')} Allow inbound access from your Lambda or local IP
183
+ ${chalk.gray('Update security groups / VPC rules for port 27017')}
184
+ `;
185
+ }
186
+
131
187
  /**
132
188
  * Gets PostgreSQL-specific troubleshooting steps
133
189
  * @returns {string} Formatted troubleshooting steps
@@ -157,11 +213,19 @@ ${chalk.gray('5.')} Verify network/firewall settings
157
213
 
158
214
  /**
159
215
  * Gets helpful error message for missing Prisma client
160
- * @param {'mongodb'|'postgresql'} dbType - Database type
216
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
161
217
  * @returns {string} Formatted error message
162
218
  */
163
219
  function getPrismaClientNotGeneratedError(dbType) {
164
- const clientName = `@prisma-${dbType}/client`;
220
+ // Normalize DocumentDB to MongoDB (they use the same Prisma client)
221
+ const normalizedType = normalizeMongoCompatible(dbType);
222
+ const clientName = `@prisma-${normalizedType}/client`;
223
+ const documentDbNote = dbType === 'documentdb'
224
+ ? [
225
+ chalk.gray(' • DocumentDB reuses the MongoDB Prisma client (prisma-mongodb)'),
226
+ chalk.gray(' • Confirm your connection string includes tls=true and retryWrites=false')
227
+ ].join('\n')
228
+ : '';
165
229
 
166
230
  return `
167
231
  ${chalk.red(`❌ Prisma client not generated for ${dbType}`)}
@@ -174,6 +238,7 @@ ${chalk.gray('This will:')}
174
238
  ${chalk.gray(' • Generate the Prisma client')} ${chalk.gray(`(${clientName})`)}
175
239
  ${chalk.gray(' • Set up database schema')}
176
240
  ${chalk.gray(' • Run migrations (PostgreSQL) or db push (MongoDB)')}
241
+ ${documentDbNote ? `${documentDbNote}\n` : ''}
177
242
  `;
178
243
  }
179
244
 
@@ -205,23 +270,30 @@ ${chalk.cyan(' Review Prisma schema')} ${chalk.gray('(node_modules/@friggframew
205
270
 
206
271
  /**
207
272
  * Gets success message for database setup completion
208
- * @param {'mongodb'|'postgresql'} dbType - Database type
273
+ * @param {'mongodb'|'postgresql'|'documentdb'} dbType - Database type
209
274
  * @param {string} stage - Deployment stage
210
275
  * @returns {string} Formatted success message
211
276
  */
212
277
  function getDatabaseSetupSuccess(dbType, stage) {
278
+ const databaseDisplayName = getDatabaseDisplayName(dbType);
279
+ const schemaAction = dbType === 'postgresql'
280
+ ? 'Migrations applied'
281
+ : dbType === 'documentdb'
282
+ ? 'Schema pushed to DocumentDB'
283
+ : 'Schema pushed to database';
284
+
213
285
  return `
214
286
  ${chalk.green('✅ Database setup completed successfully!')}
215
287
 
216
288
  ${chalk.bold('Configuration:')}
217
- ${chalk.gray(' Database type:')} ${chalk.cyan(dbType)}
289
+ ${chalk.gray(' Database type:')} ${chalk.cyan(databaseDisplayName)}
218
290
  ${chalk.gray(' Stage:')} ${chalk.cyan(stage)}
219
291
  ${chalk.gray(' Connection:')} ${chalk.green('verified')}
220
292
 
221
293
  ${chalk.bold('What happened:')}
222
294
  ${chalk.gray(' ✓')} Prisma client generated
223
295
  ${chalk.gray(' ✓')} Database connection verified
224
- ${chalk.gray(' ✓')} ${dbType === 'postgresql' ? 'Migrations applied' : 'Schema pushed to database'}
296
+ ${chalk.gray(' ✓')} ${schemaAction}
225
297
 
226
298
  ${chalk.yellow('Next steps:')}
227
299
  ${chalk.cyan(' frigg start')} ${chalk.gray('(start your application)')}