@friggframework/core 2.0.0--canary.425.2d58c19.0 → 2.0.0--canary.427.c8ff14b.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.
@@ -9,18 +9,18 @@ const router = Router();
9
9
 
10
10
  const validateApiKey = (req, res, next) => {
11
11
  const apiKey = req.headers['x-api-key'];
12
-
12
+
13
13
  if (req.path === '/health') {
14
14
  return next();
15
15
  }
16
-
16
+
17
17
  if (!apiKey || apiKey !== process.env.HEALTH_API_KEY) {
18
18
  return res.status(401).json({
19
19
  status: 'error',
20
- message: 'Unauthorized'
20
+ message: 'Unauthorized',
21
21
  });
22
22
  }
23
-
23
+
24
24
  next();
25
25
  };
26
26
 
@@ -30,7 +30,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
30
30
  return new Promise((resolve) => {
31
31
  const protocol = url.startsWith('https:') ? https : http;
32
32
  const startTime = Date.now();
33
-
33
+
34
34
  try {
35
35
  const request = protocol.get(url, { timeout }, (res) => {
36
36
  const responseTime = Date.now() - startTime;
@@ -38,7 +38,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
38
38
  status: 'healthy',
39
39
  statusCode: res.statusCode,
40
40
  responseTime,
41
- reachable: res.statusCode < 500
41
+ reachable: res.statusCode < 500,
42
42
  });
43
43
  });
44
44
 
@@ -47,7 +47,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
47
47
  status: 'unhealthy',
48
48
  error: error.message,
49
49
  responseTime: Date.now() - startTime,
50
- reachable: false
50
+ reachable: false,
51
51
  });
52
52
  });
53
53
 
@@ -57,7 +57,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
57
57
  status: 'timeout',
58
58
  error: 'Request timeout',
59
59
  responseTime: timeout,
60
- reachable: false
60
+ reachable: false,
61
61
  });
62
62
  });
63
63
  } catch (error) {
@@ -65,7 +65,7 @@ const checkExternalAPI = (url, timeout = 5000) => {
65
65
  status: 'error',
66
66
  error: error.message,
67
67
  responseTime: Date.now() - startTime,
68
- reachable: false
68
+ reachable: false,
69
69
  });
70
70
  }
71
71
  });
@@ -76,14 +76,14 @@ const getDatabaseState = () => {
76
76
  0: 'disconnected',
77
77
  1: 'connected',
78
78
  2: 'connecting',
79
- 3: 'disconnecting'
79
+ 3: 'disconnecting',
80
80
  };
81
81
  const readyState = mongoose.connection.readyState;
82
-
82
+
83
83
  return {
84
84
  readyState,
85
85
  stateName: stateMap[readyState],
86
- isConnected: readyState === 1
86
+ isConnected: readyState === 1,
87
87
  };
88
88
  };
89
89
 
@@ -91,7 +91,7 @@ const checkDatabaseHealth = async () => {
91
91
  const { stateName, isConnected } = getDatabaseState();
92
92
  const result = {
93
93
  status: isConnected ? 'healthy' : 'unhealthy',
94
- state: stateName
94
+ state: stateName,
95
95
  };
96
96
 
97
97
  if (isConnected) {
@@ -104,19 +104,20 @@ const checkDatabaseHealth = async () => {
104
104
  };
105
105
 
106
106
  const getEncryptionConfiguration = () => {
107
- const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } = process.env;
108
-
107
+ const { STAGE, BYPASS_ENCRYPTION_STAGE, KMS_KEY_ARN, AES_KEY_ID } =
108
+ process.env;
109
+
109
110
  const defaultBypassStages = ['dev', 'test', 'local'];
110
111
  const useEnv = BYPASS_ENCRYPTION_STAGE !== undefined;
111
112
  const bypassStages = useEnv
112
113
  ? BYPASS_ENCRYPTION_STAGE.split(',').map((s) => s.trim())
113
114
  : defaultBypassStages;
114
-
115
+
115
116
  const isBypassed = bypassStages.includes(STAGE);
116
117
  const hasAES = AES_KEY_ID && AES_KEY_ID.trim() !== '';
117
118
  const hasKMS = KMS_KEY_ARN && KMS_KEY_ARN.trim() !== '' && !hasAES;
118
119
  const mode = hasAES ? 'aes' : hasKMS ? 'kms' : 'none';
119
-
120
+
120
121
  return {
121
122
  stage: STAGE || null,
122
123
  isBypassed,
@@ -128,19 +129,24 @@ const getEncryptionConfiguration = () => {
128
129
 
129
130
  const createTestEncryptionModel = () => {
130
131
  const { Encrypt } = require('./../../encrypt');
131
-
132
- const testSchema = new mongoose.Schema({
133
- testSecret: { type: String, lhEncrypt: true },
134
- normalField: { type: String },
135
- nestedSecret: {
136
- value: { type: String, lhEncrypt: true }
137
- }
138
- }, { timestamps: false });
132
+
133
+ const testSchema = new mongoose.Schema(
134
+ {
135
+ testSecret: { type: String, lhEncrypt: true },
136
+ normalField: { type: String },
137
+ nestedSecret: {
138
+ value: { type: String, lhEncrypt: true },
139
+ },
140
+ },
141
+ { timestamps: false }
142
+ );
139
143
 
140
144
  testSchema.plugin(Encrypt);
141
-
142
- return mongoose.models.TestEncryption ||
143
- mongoose.model('TestEncryption', testSchema);
145
+
146
+ return (
147
+ mongoose.models.TestEncryption ||
148
+ mongoose.model('TestEncryption', testSchema)
149
+ );
144
150
  };
145
151
 
146
152
  const createTestDocument = async (TestModel) => {
@@ -148,21 +154,71 @@ const createTestDocument = async (TestModel) => {
148
154
  testSecret: 'This is a secret value that should be encrypted',
149
155
  normalField: 'This is a normal field that should not be encrypted',
150
156
  nestedSecret: {
151
- value: 'This is a nested secret that should be encrypted'
152
- }
157
+ value: 'This is a nested secret that should be encrypted',
158
+ },
153
159
  };
154
160
 
161
+ console.log('🔧 Creating test document with encryption...');
155
162
  const testDoc = new TestModel(testData);
156
- await testDoc.save();
157
-
158
- return { testDoc, testData };
163
+
164
+ // Add timeout and detailed error logging
165
+ const savePromise = testDoc.save();
166
+ const timeoutPromise = new Promise((_, reject) => {
167
+ setTimeout(
168
+ () =>
169
+ reject(new Error('Save operation timed out after 30 seconds')),
170
+ 30000
171
+ );
172
+ });
173
+
174
+ try {
175
+ console.log('💾 Attempting to save document...');
176
+ const startTime = Date.now();
177
+
178
+ await Promise.race([savePromise, timeoutPromise]);
179
+
180
+ const duration = Date.now() - startTime;
181
+ console.log(`✅ Document saved successfully in ${duration}ms`);
182
+
183
+ return { testDoc, testData };
184
+ } catch (error) {
185
+ console.error('❌ Save operation failed:', {
186
+ errorName: error.name,
187
+ errorMessage: error.message,
188
+ errorStack: error.stack,
189
+ mongooseConnectionState: testDoc.db.readyState,
190
+ modelName: TestModel.modelName,
191
+ });
192
+
193
+ // Try to get more details about the connection
194
+ if (mongoose.connection && mongoose.connection.db) {
195
+ try {
196
+ const admin = mongoose.connection.db.admin();
197
+ const serverStatus = await admin.serverStatus();
198
+ console.log('📊 DocumentDB Server Status:', {
199
+ version: serverStatus.version,
200
+ uptime: serverStatus.uptime,
201
+ connections: serverStatus.connections,
202
+ });
203
+ } catch (statusError) {
204
+ console.error(
205
+ '❌ Could not get server status:',
206
+ statusError.message
207
+ );
208
+ }
209
+ }
210
+
211
+ throw error;
212
+ }
159
213
  };
160
214
 
161
215
  const verifyDecryption = (retrievedDoc, originalData) => {
162
- return retrievedDoc &&
216
+ return (
217
+ retrievedDoc &&
163
218
  retrievedDoc.testSecret === originalData.testSecret &&
164
219
  retrievedDoc.normalField === originalData.normalField &&
165
- retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value;
220
+ retrievedDoc.nestedSecret?.value === originalData.nestedSecret.value
221
+ );
166
222
  };
167
223
 
168
224
  const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
@@ -171,70 +227,85 @@ const verifyEncryptionInDatabase = async (testDoc, originalData, TestModel) => {
171
227
  .collection(collectionName)
172
228
  .findOne({ _id: testDoc._id });
173
229
 
174
- const secretIsEncrypted = rawDoc &&
175
- typeof rawDoc.testSecret === 'string' &&
176
- rawDoc.testSecret.includes(':') &&
230
+ const secretIsEncrypted =
231
+ rawDoc &&
232
+ typeof rawDoc.testSecret === 'string' &&
233
+ rawDoc.testSecret.includes(':') &&
177
234
  rawDoc.testSecret !== originalData.testSecret;
178
-
179
- const nestedIsEncrypted = rawDoc?.nestedSecret?.value &&
235
+
236
+ const nestedIsEncrypted =
237
+ rawDoc?.nestedSecret?.value &&
180
238
  typeof rawDoc.nestedSecret.value === 'string' &&
181
- rawDoc.nestedSecret.value.includes(':') &&
239
+ rawDoc.nestedSecret.value.includes(':') &&
182
240
  rawDoc.nestedSecret.value !== originalData.nestedSecret.value;
183
-
184
- const normalNotEncrypted = rawDoc &&
185
- rawDoc.normalField === originalData.normalField;
241
+
242
+ const normalNotEncrypted =
243
+ rawDoc && rawDoc.normalField === originalData.normalField;
186
244
 
187
245
  return {
188
246
  secretIsEncrypted,
189
247
  nestedIsEncrypted,
190
- normalNotEncrypted
248
+ normalNotEncrypted,
191
249
  };
192
250
  };
193
251
 
194
252
  const evaluateEncryptionTestResults = (decryptionWorks, encryptionResults) => {
195
- const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } = encryptionResults;
196
-
197
- if (decryptionWorks && secretIsEncrypted && nestedIsEncrypted && normalNotEncrypted) {
253
+ const { secretIsEncrypted, nestedIsEncrypted, normalNotEncrypted } =
254
+ encryptionResults;
255
+
256
+ if (
257
+ decryptionWorks &&
258
+ secretIsEncrypted &&
259
+ nestedIsEncrypted &&
260
+ normalNotEncrypted
261
+ ) {
198
262
  return {
199
263
  status: 'enabled',
200
- testResult: 'Encryption and decryption verified successfully'
264
+ testResult: 'Encryption and decryption verified successfully',
201
265
  };
202
266
  }
203
-
267
+
204
268
  if (decryptionWorks && (!secretIsEncrypted || !nestedIsEncrypted)) {
205
269
  return {
206
270
  status: 'unhealthy',
207
- testResult: 'Fields are not being encrypted in database'
271
+ testResult: 'Fields are not being encrypted in database',
208
272
  };
209
273
  }
210
-
274
+
211
275
  if (decryptionWorks && !normalNotEncrypted) {
212
276
  return {
213
277
  status: 'unhealthy',
214
- testResult: 'Normal fields are being incorrectly encrypted'
278
+ testResult: 'Normal fields are being incorrectly encrypted',
215
279
  };
216
280
  }
217
-
281
+
218
282
  return {
219
283
  status: 'unhealthy',
220
- testResult: 'Decryption failed or data mismatch'
284
+ testResult: 'Decryption failed or data mismatch',
221
285
  };
222
286
  };
223
287
 
224
288
  const testEncryption = async () => {
225
289
  const TestModel = createTestEncryptionModel();
226
290
  const { testDoc, testData } = await createTestDocument(TestModel);
227
-
291
+
228
292
  try {
229
293
  const retrievedDoc = await TestModel.findById(testDoc._id);
230
294
  const decryptionWorks = verifyDecryption(retrievedDoc, testData);
231
- const encryptionResults = await verifyEncryptionInDatabase(testDoc, testData, TestModel);
232
-
233
- const evaluation = evaluateEncryptionTestResults(decryptionWorks, encryptionResults);
234
-
295
+ const encryptionResults = await verifyEncryptionInDatabase(
296
+ testDoc,
297
+ testData,
298
+ TestModel
299
+ );
300
+
301
+ const evaluation = evaluateEncryptionTestResults(
302
+ decryptionWorks,
303
+ encryptionResults
304
+ );
305
+
235
306
  return {
236
307
  ...evaluation,
237
- encryptionWorks: decryptionWorks
308
+ encryptionWorks: decryptionWorks,
238
309
  };
239
310
  } finally {
240
311
  await TestModel.deleteOne({ _id: testDoc._id });
@@ -243,12 +314,12 @@ const testEncryption = async () => {
243
314
 
244
315
  const checkEncryptionHealth = async () => {
245
316
  const config = getEncryptionConfiguration();
246
-
317
+
247
318
  if (config.isBypassed || config.mode === 'none') {
248
- const testResult = config.isBypassed
249
- ? 'Encryption bypassed for this stage'
319
+ const testResult = config.isBypassed
320
+ ? 'Encryption bypassed for this stage'
250
321
  : 'No encryption keys configured';
251
-
322
+
252
323
  return {
253
324
  status: 'disabled',
254
325
  mode: config.mode,
@@ -258,14 +329,14 @@ const checkEncryptionHealth = async () => {
258
329
  encryptionWorks: false,
259
330
  debug: {
260
331
  hasKMS: config.hasKMS,
261
- hasAES: config.hasAES
262
- }
332
+ hasAES: config.hasAES,
333
+ },
263
334
  };
264
335
  }
265
336
 
266
337
  try {
267
338
  const testResults = await testEncryption();
268
-
339
+
269
340
  return {
270
341
  ...testResults,
271
342
  mode: config.mode,
@@ -273,8 +344,8 @@ const checkEncryptionHealth = async () => {
273
344
  stage: config.stage,
274
345
  debug: {
275
346
  hasKMS: config.hasKMS,
276
- hasAES: config.hasAES
277
- }
347
+ hasAES: config.hasAES,
348
+ },
278
349
  };
279
350
  } catch (error) {
280
351
  return {
@@ -286,8 +357,8 @@ const checkEncryptionHealth = async () => {
286
357
  encryptionWorks: false,
287
358
  debug: {
288
359
  hasKMS: config.hasKMS,
289
- hasAES: config.hasAES
290
- }
360
+ hasAES: config.hasAES,
361
+ },
291
362
  };
292
363
  }
293
364
  };
@@ -295,25 +366,28 @@ const checkEncryptionHealth = async () => {
295
366
  const checkExternalAPIs = async () => {
296
367
  const apis = [
297
368
  { name: 'github', url: 'https://api.github.com/status' },
298
- { name: 'npm', url: 'https://registry.npmjs.org' }
369
+ { name: 'npm', url: 'https://registry.npmjs.org' },
299
370
  ];
300
371
 
301
372
  const results = await Promise.all(
302
- apis.map(api =>
303
- checkExternalAPI(api.url).then(result => ({ name: api.name, ...result }))
373
+ apis.map((api) =>
374
+ checkExternalAPI(api.url).then((result) => ({
375
+ name: api.name,
376
+ ...result,
377
+ }))
304
378
  )
305
379
  );
306
-
380
+
307
381
  const apiStatuses = {};
308
382
  let allReachable = true;
309
-
383
+
310
384
  results.forEach(({ name, ...checkResult }) => {
311
385
  apiStatuses[name] = checkResult;
312
386
  if (!checkResult.reachable) {
313
387
  allReachable = false;
314
388
  }
315
389
  });
316
-
390
+
317
391
  return { apiStatuses, allReachable };
318
392
  };
319
393
 
@@ -321,7 +395,7 @@ const checkIntegrations = () => {
321
395
  const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
322
396
  ? moduleFactory.moduleTypes
323
397
  : [];
324
-
398
+
325
399
  const integrationTypes = Array.isArray(integrationFactory.integrationTypes)
326
400
  ? integrationFactory.integrationTypes
327
401
  : [];
@@ -345,7 +419,7 @@ const buildHealthCheckResponse = (startTime) => {
345
419
  status: 'healthy',
346
420
  timestamp: new Date().toISOString(),
347
421
  checks: {},
348
- calculateResponseTime: () => Date.now() - startTime
422
+ calculateResponseTime: () => Date.now() - startTime,
349
423
  };
350
424
  };
351
425
 
@@ -353,7 +427,7 @@ router.get('/health', async (_req, res) => {
353
427
  const status = {
354
428
  status: 'ok',
355
429
  timestamp: new Date().toISOString(),
356
- service: 'frigg-core-api'
430
+ service: 'frigg-core-api',
357
431
  };
358
432
 
359
433
  res.status(200).json(status);
@@ -372,7 +446,7 @@ router.get('/health/detailed', async (_req, res) => {
372
446
  } catch (error) {
373
447
  response.checks.database = {
374
448
  status: 'unhealthy',
375
- error: error.message
449
+ error: error.message,
376
450
  };
377
451
  response.status = 'unhealthy';
378
452
  }
@@ -385,7 +459,7 @@ router.get('/health/detailed', async (_req, res) => {
385
459
  } catch (error) {
386
460
  response.checks.encryption = {
387
461
  status: 'unhealthy',
388
- error: error.message
462
+ error: error.message,
389
463
  };
390
464
  response.status = 'unhealthy';
391
465
  }
@@ -401,7 +475,7 @@ router.get('/health/detailed', async (_req, res) => {
401
475
  } catch (error) {
402
476
  response.checks.integrations = {
403
477
  status: 'unhealthy',
404
- error: error.message
478
+ error: error.message,
405
479
  };
406
480
  response.status = 'unhealthy';
407
481
  }
@@ -416,14 +490,14 @@ router.get('/health/detailed', async (_req, res) => {
416
490
  router.get('/health/live', (_req, res) => {
417
491
  res.status(200).json({
418
492
  status: 'alive',
419
- timestamp: new Date().toISOString()
493
+ timestamp: new Date().toISOString(),
420
494
  });
421
495
  });
422
496
 
423
497
  router.get('/health/ready', async (_req, res) => {
424
498
  const dbState = getDatabaseState();
425
499
  const isDbReady = dbState.isConnected;
426
-
500
+
427
501
  let areModulesReady = false;
428
502
  try {
429
503
  const moduleTypes = Array.isArray(moduleFactory.moduleTypes)
@@ -441,11 +515,11 @@ router.get('/health/ready', async (_req, res) => {
441
515
  timestamp: new Date().toISOString(),
442
516
  checks: {
443
517
  database: isDbReady,
444
- modules: areModulesReady
445
- }
518
+ modules: areModulesReady,
519
+ },
446
520
  });
447
521
  });
448
522
 
449
523
  const handler = createAppHandler('HTTP Event: Health', router);
450
524
 
451
- module.exports = { handler, router };
525
+ module.exports = { handler, router };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.425.2d58c19.0",
4
+ "version": "2.0.0--canary.427.c8ff14b.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
7
  "aws-sdk": "^2.1200.0",
@@ -22,9 +22,9 @@
22
22
  "uuid": "^9.0.1"
23
23
  },
24
24
  "devDependencies": {
25
- "@friggframework/eslint-config": "2.0.0--canary.425.2d58c19.0",
26
- "@friggframework/prettier-config": "2.0.0--canary.425.2d58c19.0",
27
- "@friggframework/test": "2.0.0--canary.425.2d58c19.0",
25
+ "@friggframework/eslint-config": "2.0.0--canary.427.c8ff14b.0",
26
+ "@friggframework/prettier-config": "2.0.0--canary.427.c8ff14b.0",
27
+ "@friggframework/test": "2.0.0--canary.427.c8ff14b.0",
28
28
  "@types/lodash": "4.17.15",
29
29
  "@typescript-eslint/eslint-plugin": "^8.0.0",
30
30
  "chai": "^4.3.6",
@@ -56,5 +56,5 @@
56
56
  "publishConfig": {
57
57
  "access": "public"
58
58
  },
59
- "gitHead": "2d58c196b068fafce914e0fc631d72a30b745f31"
59
+ "gitHead": "c8ff14b4f6c1a21bd6c254ad12f1c9963b0374db"
60
60
  }