@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -0,0 +1,50 @@
1
+ const Boom = require('@hapi/boom');
2
+
3
+ /**
4
+ * Delete Credential For User Use Case
5
+ * Removes a credential after verifying ownership
6
+ *
7
+ * Business Logic:
8
+ * - Verify credential exists
9
+ * - Verify credential belongs to user (authorization)
10
+ * - Delete the credential
11
+ * - Return success status
12
+ */
13
+ class DeleteCredentialForUser {
14
+ constructor({ credentialRepository }) {
15
+ this.credentialRepository = credentialRepository;
16
+ }
17
+
18
+ /**
19
+ * Execute the use case
20
+ * @param {string} credentialId - Credential ID
21
+ * @param {string} userId - User ID (for ownership verification)
22
+ * @returns {Promise<Object>} Deletion result { deletedCount: number }
23
+ */
24
+ async execute(credentialId, userId) {
25
+ // Check if credential exists
26
+ const credential = await this.credentialRepository.findCredentialById(
27
+ credentialId
28
+ );
29
+
30
+ if (!credential) {
31
+ throw Boom.notFound(`Credential ${credentialId} not found`);
32
+ }
33
+
34
+ // Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
35
+ if (credential.userId.toString() !== userId.toString()) {
36
+ throw Boom.forbidden(
37
+ 'You do not have permission to delete this credential'
38
+ );
39
+ }
40
+
41
+ // Delete the credential
42
+ const result = await this.credentialRepository.deleteCredentialById(
43
+ credentialId
44
+ );
45
+
46
+ return result;
47
+ }
48
+ }
49
+
50
+ module.exports = { DeleteCredentialForUser };
@@ -1,20 +1,38 @@
1
+ const Boom = require('@hapi/boom');
2
+
3
+ /**
4
+ * Get Credential For User Use Case
5
+ * Retrieves a single credential after verifying ownership
6
+ *
7
+ * Business Logic:
8
+ * - Verify credential exists
9
+ * - Verify credential belongs to user (authorization)
10
+ * - Return credential
11
+ */
1
12
  class GetCredentialForUser {
2
13
  constructor({ credentialRepository }) {
3
14
  this.credentialRepository = credentialRepository;
4
15
  }
5
16
 
17
+ /**
18
+ * Execute the use case
19
+ * @param {string} credentialId - Credential ID
20
+ * @param {string} userId - User ID (for ownership verification)
21
+ * @returns {Promise<Object>} Credential object
22
+ */
6
23
  async execute(credentialId, userId) {
7
24
  const credential = await this.credentialRepository.findCredentialById(
8
25
  credentialId
9
26
  );
10
27
 
11
28
  if (!credential) {
12
- throw new Error(`Credential with id ${credentialId} not found`);
29
+ throw Boom.notFound(`Credential ${credentialId} not found`);
13
30
  }
14
31
 
32
+ // Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
15
33
  if (credential.userId.toString() !== userId.toString()) {
16
- throw new Error(
17
- `Credential ${credentialId} does not belong to user ${userId}`
34
+ throw Boom.forbidden(
35
+ 'You do not have permission to access this credential'
18
36
  );
19
37
  }
20
38
 
@@ -0,0 +1,31 @@
1
+ /**
2
+ * List Credentials For User Use Case
3
+ * Retrieves all credentials belonging to a specific user
4
+ *
5
+ * Business Logic:
6
+ * - Fetches credentials for the authenticated user
7
+ * - Returns list of credentials (sensitive data filtered by handler)
8
+ * - Repository returns an array when filtering by userId only
9
+ */
10
+ class ListCredentialsForUser {
11
+ constructor({ credentialRepository }) {
12
+ this.credentialRepository = credentialRepository;
13
+ }
14
+
15
+ /**
16
+ * Execute the use case
17
+ * @param {string} userId - User ID
18
+ * @returns {Promise<Array>} List of credentials (empty array if none found)
19
+ */
20
+ async execute(userId) {
21
+ const credentials = await this.credentialRepository.findCredential({
22
+ userId,
23
+ });
24
+
25
+ // Repository returns array for userId-only queries
26
+ // Ensure we always return an array (defensive programming)
27
+ return Array.isArray(credentials) ? credentials : [];
28
+ }
29
+ }
30
+
31
+ module.exports = { ListCredentialsForUser };
@@ -0,0 +1,103 @@
1
+ const Boom = require('@hapi/boom');
2
+
3
+ /**
4
+ * Reauthorize Credential Use Case
5
+ * Re-authorizes an existing credential with new authentication data
6
+ * Supports both single-step and multi-step authorization flows
7
+ *
8
+ * Business Logic:
9
+ * - Verify credential exists and belongs to user
10
+ * - Load the appropriate module for the credential type
11
+ * - Process authorization callback (OAuth code, API keys, etc.)
12
+ * - Update credential with new tokens
13
+ * - Return success or next step requirements
14
+ */
15
+ class ReauthorizeCredential {
16
+ constructor({ credentialRepository, moduleRepository }) {
17
+ this.credentialRepository = credentialRepository;
18
+ this.moduleRepository = moduleRepository;
19
+ }
20
+
21
+ /**
22
+ * Execute the use case
23
+ * @param {string} credentialId - Credential ID to reauthorize
24
+ * @param {string} userId - User ID (for ownership verification)
25
+ * @param {Object} authData - Authorization data
26
+ * @param {number} [step=1] - Current step in multi-step flow
27
+ * @param {string} [sessionId] - Session ID for multi-step flows
28
+ * @returns {Promise<Object>} Success response or next step requirements
29
+ */
30
+ async execute(credentialId, userId, authData, step = 1, sessionId = null) {
31
+ // Check if credential exists
32
+ const credential = await this.credentialRepository.findCredentialById(
33
+ credentialId
34
+ );
35
+
36
+ if (!credential) {
37
+ throw Boom.notFound(`Credential ${credentialId} not found`);
38
+ }
39
+
40
+ // Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
41
+ if (credential.userId.toString() !== userId.toString()) {
42
+ throw Boom.forbidden(
43
+ 'You do not have permission to reauthorize this credential'
44
+ );
45
+ }
46
+
47
+ // Load the module for this credential type
48
+ const module = await this.moduleRepository.findModuleById(
49
+ credential.id
50
+ );
51
+
52
+ if (!module) {
53
+ throw Boom.badRequest(
54
+ `Module not found for credential type: ${
55
+ credential.type || 'unknown'
56
+ }`
57
+ );
58
+ }
59
+
60
+ // Process the authorization callback
61
+ const result = await module.processAuthorizationCallback({
62
+ credentialId,
63
+ userId,
64
+ data: authData,
65
+ step,
66
+ sessionId,
67
+ });
68
+
69
+ // Multi-step flow - return next step requirements
70
+ if (result.step && result.step > 1) {
71
+ return {
72
+ step: result.step,
73
+ totalSteps: result.totalSteps,
74
+ sessionId: result.sessionId,
75
+ requirements: result.requirements,
76
+ message: result.message,
77
+ };
78
+ }
79
+
80
+ // Single-step or final step - update credential and return success
81
+ if (result.success) {
82
+ // Fetch the updated credential to get the new authIsValid status
83
+ const updatedCredential =
84
+ await this.credentialRepository.findCredentialById(
85
+ credentialId
86
+ );
87
+
88
+ return {
89
+ success: true,
90
+ credential_id: credentialId,
91
+ authIsValid: updatedCredential?.authIsValid || true,
92
+ ...(result.message && { message: result.message }),
93
+ };
94
+ }
95
+
96
+ // If we get here, something unexpected happened
97
+ throw new Error(
98
+ 'Authorization callback did not return expected result format'
99
+ );
100
+ }
101
+ }
102
+
103
+ module.exports = { ReauthorizeCredential };
@@ -8,8 +8,11 @@ class UpdateAuthenticationStatus {
8
8
  * @param {boolean} authIsValid
9
9
  */
10
10
  async execute(credentialId, authIsValid) {
11
- await this.credentialRepository.updateAuthenticationStatus(credentialId, authIsValid);
11
+ await this.credentialRepository.updateAuthenticationStatus(
12
+ credentialId,
13
+ authIsValid
14
+ );
12
15
  }
13
16
  }
14
17
 
15
- module.exports = { UpdateAuthenticationStatus };
18
+ module.exports = { UpdateAuthenticationStatus };
@@ -15,9 +15,9 @@ MongoDB does not allow creating collections (namespaces) inside multi-document t
15
15
 
16
16
  ### Technical Details
17
17
 
18
- - **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
19
- - **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
20
- - **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
18
+ - **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
19
+ - **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
20
+ - **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
21
21
 
22
22
  ## Solution
23
23
 
@@ -37,30 +37,34 @@ This follows the **"fail fast"** principle and ensures consistent state across a
37
37
  ### Changes Made
38
38
 
39
39
  1. **Created MongoDB Schema Initialization** (`packages/core/database/utils/mongodb-schema-init.js`)
40
- - `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
41
- - `getPrismaCollections()` - Returns list of all Prisma collection names
42
- - `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
43
- - Only runs for MongoDB (skips PostgreSQL)
44
- - Fails fast if database not connected
40
+
41
+ - `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
42
+ - `getPrismaCollections()` - Returns list of all Prisma collection names
43
+ - `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
44
+ - Only runs for MongoDB (skips PostgreSQL)
45
+ - Fails fast if database not connected
45
46
 
46
47
  2. **Created MongoDB Collection Utilities** (`packages/core/database/utils/mongodb-collection-utils.js`)
47
- - `ensureCollectionExists(collectionName)` - Ensures a single collection exists
48
- - `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
49
- - `collectionExists(collectionName)` - Checks if a collection exists
50
- - Handles race conditions gracefully (NamespaceExists errors)
48
+
49
+ - `ensureCollectionExists(collectionName)` - Ensures a single collection exists
50
+ - `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
51
+ - `collectionExists(collectionName)` - Checks if a collection exists
52
+ - Handles race conditions gracefully (NamespaceExists errors)
51
53
 
52
54
  3. **Integrated into Database Connection** (`packages/core/database/prisma.js`)
53
- - Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
54
- - Ensures all collections exist before application handles requests
55
+
56
+ - Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
57
+ - Ensures all collections exist before application handles requests
55
58
 
56
59
  4. **Updated Health Check Repository** (`packages/core/database/repositories/health-check-repository-mongodb.js`)
57
- - Removed per-operation collection existence checks
58
- - Added documentation noting schema is initialized at startup
60
+
61
+ - Removed per-operation collection existence checks
62
+ - Added documentation noting schema is initialized at startup
59
63
 
60
64
  5. **Added Comprehensive Tests**
61
- - `mongodb-schema-init.test.js` - Tests schema initialization system
62
- - `mongodb-collection-utils.test.js` - Tests collection utility functions
63
- - Tests error handling, race conditions, and edge cases
65
+ - `mongodb-schema-init.test.js` - Tests schema initialization system
66
+ - `mongodb-collection-utils.test.js` - Tests collection utility functions
67
+ - Tests error handling, race conditions, and edge cases
64
68
 
65
69
  ### Implementation Flow
66
70
 
@@ -93,23 +97,26 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
93
97
  ## Benefits
94
98
 
95
99
  ### Immediate Benefits
96
- - ✅ Fixes encryption health check failures on fresh databases
97
- - ✅ Prevents transaction namespace errors across **all** Prisma operations
98
- - ✅ No per-operation overhead - collections created once at startup
99
- - ✅ Fail fast - database issues discovered immediately at startup
100
- - ✅ Idempotent - safe to run multiple times and across multiple instances
100
+
101
+ - ✅ Fixes encryption health check failures on fresh databases
102
+ - ✅ Prevents transaction namespace errors across **all** Prisma operations
103
+ - ✅ No per-operation overhead - collections created once at startup
104
+ - ✅ Fail fast - database issues discovered immediately at startup
105
+ - ✅ Idempotent - safe to run multiple times and across multiple instances
101
106
 
102
107
  ### Architectural Benefits
103
- - ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
104
- - ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
105
- - ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
106
- - ✅ **No repository-level checks needed**: All repositories benefit automatically
107
- - ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
108
+
109
+ - ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
110
+ - ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
111
+ - ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
112
+ - ✅ **No repository-level checks needed**: All repositories benefit automatically
113
+ - ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
108
114
 
109
115
  ### Operational Benefits
110
- - ✅ **Predictable startup**: Clear logging of schema initialization
111
- - ✅ **Zero runtime overhead**: Collections created once, not on every operation
112
- - ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
116
+
117
+ - ✅ **Predictable startup**: Clear logging of schema initialization
118
+ - ✅ **Zero runtime overhead**: Collections created once, not on every operation
119
+ - ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
113
120
 
114
121
  ## Design Decisions
115
122
 
@@ -118,16 +125,19 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
118
125
  We considered two approaches:
119
126
 
120
127
  **❌ Per-Operation Checks (Initial approach)**
128
+
121
129
  ```javascript
122
130
  async createCredential(data) {
123
131
  await ensureCollectionExists('Credential'); // Check every time
124
132
  return await prisma.credential.create({ data });
125
133
  }
126
134
  ```
127
- - Pros: Guarantees collection exists before each operation
128
- - Cons: Runtime overhead, repeated checks, scattered logic
135
+
136
+ - Pros: Guarantees collection exists before each operation
137
+ - Cons: Runtime overhead, repeated checks, scattered logic
129
138
 
130
139
  **✅ Startup Initialization (Final approach)**
140
+
131
141
  ```javascript
132
142
  // Once at startup
133
143
  await connectPrisma(); // Initializes all collections
@@ -137,8 +147,9 @@ async createCredential(data) {
137
147
  return await prisma.credential.create({ data }); // No checks needed
138
148
  }
139
149
  ```
140
- - Pros: Zero runtime overhead, centralized logic, fail fast, consistent
141
- - Cons: Requires database connection at startup (already required)
150
+
151
+ - Pros: Zero runtime overhead, centralized logic, fail fast, consistent
152
+ - Cons: Requires database connection at startup (already required)
142
153
 
143
154
  ### Benefits of Startup Approach
144
155
 
@@ -159,6 +170,7 @@ MongoDB schema initialization complete - 13 collections verified (45ms)
159
170
  ```
160
171
 
161
172
  On subsequent startups (collections already exist):
173
+
162
174
  ```
163
175
  Initializing MongoDB schema - ensuring all collections exist...
164
176
  MongoDB schema initialization complete - 13 collections verified (12ms)
@@ -166,33 +178,41 @@ MongoDB schema initialization complete - 13 collections verified (12ms)
166
178
 
167
179
  ## References
168
180
 
169
- - [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
170
- - [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
171
- - [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
172
- - [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
181
+ - [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
182
+ - [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
183
+ - [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
184
+ - [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
173
185
 
174
186
  ## Future Considerations
175
187
 
176
188
  ### Automatic Schema Sync
189
+
177
190
  Consider enhancing the system to:
178
- - Parse Prisma schema file dynamically to extract collection names
179
- - Auto-detect schema changes and create new collections
180
- - Provide CLI command for manual schema initialization
191
+
192
+ - Parse Prisma schema file dynamically to extract collection names
193
+ - Auto-detect schema changes and create new collections
194
+ - Provide CLI command for manual schema initialization
181
195
 
182
196
  ### Migration Support
197
+
183
198
  For production deployments with existing data:
184
- - Document migration procedures for new collections
185
- - Consider pre-migration scripts for blue-green deployments
186
- - Add health check for schema initialization status
199
+
200
+ - Document migration procedures for new collections
201
+ - Consider pre-migration scripts for blue-green deployments
202
+ - Add health check for schema initialization status
187
203
 
188
204
  ### Multi-Database Support
205
+
189
206
  The system already handles:
190
- - ✅ MongoDB - Full schema initialization
191
- - ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
192
- - Consider adding explicit migration support for DocumentDB-specific features
207
+
208
+ - ✅ MongoDB - Full schema initialization
209
+ - ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
210
+ - Consider adding explicit migration support for DocumentDB-specific features
193
211
 
194
212
  ### Index Creation
213
+
195
214
  Future enhancement could also create indexes at startup:
196
- - Parse Prisma schema for `@@index` directives
197
- - Create indexes if they don't exist
198
- - Provide index health checks
215
+
216
+ - Parse Prisma schema for `@@index` directives
217
+ - Create indexes if they don't exist
218
+ - Provide index health checks
@@ -5,7 +5,7 @@
5
5
 
6
6
  /**
7
7
  * Determines database type from environment or app definition
8
- *
8
+ *
9
9
  * Detection order:
10
10
  * 1. DB_TYPE environment variable (set for migration handlers)
11
11
  * 2. App definition (backend/index.js Definition.database configuration)
@@ -20,67 +20,21 @@ function getDatabaseType() {
20
20
  }
21
21
 
22
22
  // Fallback: Load app definition
23
+ // Uses loadAppDefinition() which respects setAppDefinition() cache.
24
+ // This is critical for platforms like Netlify where process.cwd()-based
25
+ // discovery fails at runtime (process.cwd() is /var/task, not the project root).
23
26
  try {
24
- const path = require('node:path');
25
- const fs = require('node:fs');
26
- const { findNearestBackendPackageJson } = require('../utils');
27
-
28
- let backendIndexPath;
29
- let database;
30
- const backendPackagePath = findNearestBackendPackageJson();
31
-
32
- if (!backendPackagePath) {
33
- throw new Error(
34
- '[Frigg] Cannot find backend package.json. ' +
35
- 'Ensure backend/package.json exists in your project.'
36
- );
37
- }
38
-
39
- const backendDir = path.dirname(backendPackagePath);
40
- backendIndexPath = path.join(backendDir, 'index.js');
41
-
42
- if (!fs.existsSync(backendIndexPath)) {
43
- throw new Error(
44
- `[Frigg] Backend index.js not found at ${backendIndexPath}. ` +
45
- 'Ensure backend/index.js exists with a Definition export.'
46
- );
47
- }
48
-
49
- let backendModule;
50
- try {
51
- backendModule = require(backendIndexPath);
52
- } catch (requireError) {
53
- // Extract the actual file with the error from the stack trace
54
- // Skip internal Node.js files (node:internal/*) and find first user file
55
- let errorFile = 'unknown file';
56
- const stackLines = requireError.stack?.split('\n') || [];
57
-
58
- for (const line of stackLines) {
59
- // Match file paths in stack trace, excluding node:internal
60
- const match = line.match(/\(([^)]+\.js):\d+:\d+\)/) || line.match(/at ([^(]+\.js):\d+:\d+/);
61
- if (match && match[1] && !match[1].includes('node:internal')) {
62
- errorFile = match[1];
63
- break;
64
- }
65
- }
66
-
67
- // Provide better error context for syntax/runtime errors
68
- throw new Error(
69
- `[Frigg] Failed to load app definition from ${backendIndexPath}\n` +
70
- `Error: ${requireError.message}\n` +
71
- `File with error: ${errorFile}\n` +
72
- `\nFull stack trace:\n${requireError.stack}\n\n` +
73
- 'This error occurred while loading your app definition or its dependencies. ' +
74
- 'Check the file listed above for syntax errors (trailing commas, missing brackets, etc.)'
75
- );
76
- }
27
+ const {
28
+ loadAppDefinition,
29
+ } = require('../handlers/app-definition-loader');
77
30
 
78
- database = backendModule?.Definition?.database;
31
+ const { appDefinition } = loadAppDefinition();
32
+ const database = appDefinition?.database;
79
33
 
80
34
  if (!database) {
81
35
  throw new Error(
82
36
  '[Frigg] App definition missing database configuration. ' +
83
- `Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to ${backendIndexPath}`
37
+ 'Add database: { postgres: { enable: true } } (or mongoDB/documentDB) to your backend/index.js'
84
38
  );
85
39
  }
86
40
 
@@ -98,7 +52,7 @@ function getDatabaseType() {
98
52
 
99
53
  throw new Error(
100
54
  '[Frigg] No database enabled in app definition. ' +
101
- 'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
55
+ 'Set one of: database.postgres.enable, database.mongoDB.enable, or database.documentDB.enable to true'
102
56
  );
103
57
  } catch (error) {
104
58
  // Re-throw with context if it's our error
@@ -150,5 +104,5 @@ Object.defineProperty(module.exports, 'DB_TYPE', {
150
104
  return cachedDbType;
151
105
  },
152
106
  enumerable: true,
153
- configurable: true
154
- });
107
+ configurable: true,
108
+ });
@@ -1,5 +1,8 @@
1
1
  const { Cryptor } = require('../encrypt/Cryptor');
2
- const { getEncryptedFields, loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
2
+ const {
3
+ getEncryptedFields,
4
+ loadCustomEncryptionSchema,
5
+ } = require('./encryption/encryption-schema-registry');
3
6
 
4
7
  /**
5
8
  * Encryption service specifically for DocumentDB repositories
@@ -51,8 +54,11 @@ class DocumentDBEncryptionService {
51
54
  loadCustomEncryptionSchema();
52
55
 
53
56
  // Match logic from packages/core/database/prisma.js
54
- const stage = process.env.STAGE || process.env.NODE_ENV || 'development';
55
- const bypassEncryption = ['dev', 'test', 'local'].includes(stage.toLowerCase());
57
+ const stage =
58
+ process.env.STAGE || process.env.NODE_ENV || 'development';
59
+ const bypassEncryption = ['dev', 'test', 'local'].includes(
60
+ stage.toLowerCase()
61
+ );
56
62
 
57
63
  if (bypassEncryption) {
58
64
  this.cryptor = null;
@@ -61,11 +67,17 @@ class DocumentDBEncryptionService {
61
67
  }
62
68
 
63
69
  // Determine encryption method (ensure boolean values)
64
- const hasKMS = !!(process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== '');
65
- const hasAES = !!(process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== '');
70
+ const hasKMS = !!(
71
+ process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== ''
72
+ );
73
+ const hasAES = !!(
74
+ process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== ''
75
+ );
66
76
 
67
77
  if (!hasKMS && !hasAES) {
68
- console.warn('[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.');
78
+ console.warn(
79
+ '[DocumentDBEncryptionService] No encryption keys configured. Encryption disabled.'
80
+ );
69
81
  this.cryptor = null;
70
82
  this.enabled = false;
71
83
  return;
@@ -202,14 +214,16 @@ class DocumentDBEncryptionService {
202
214
 
203
215
  try {
204
216
  // Convert to string if needed
205
- const stringValue = typeof value === 'string'
206
- ? value
207
- : JSON.stringify(value);
217
+ const stringValue =
218
+ typeof value === 'string' ? value : JSON.stringify(value);
208
219
 
209
220
  // Encrypt using Cryptor
210
221
  current[fieldName] = await this.cryptor.encrypt(stringValue);
211
222
  } catch (error) {
212
- console.error(`[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`, error.message);
223
+ console.error(
224
+ `[DocumentDBEncryptionService] Failed to encrypt ${modelName}.${fieldPath}:`,
225
+ error.message
226
+ );
213
227
  throw error;
214
228
  }
215
229
  }
@@ -261,7 +275,7 @@ class DocumentDBEncryptionService {
261
275
  modelName,
262
276
  fieldPath,
263
277
  encryptedValuePrefix: encryptedValue.substring(0, 20),
264
- errorMessage: error.message
278
+ errorMessage: error.message,
265
279
  };
266
280
 
267
281
  console.error(
@@ -270,7 +284,9 @@ class DocumentDBEncryptionService {
270
284
  );
271
285
 
272
286
  // Throw error to fail fast - don't silently corrupt data
273
- throw new Error(`Decryption failed for ${modelName}.${fieldPath}: ${error.message}`);
287
+ throw new Error(
288
+ `Decryption failed for ${modelName}.${fieldPath}: ${error.message}`
289
+ );
274
290
  }
275
291
  }
276
292
 
@@ -307,7 +323,7 @@ class DocumentDBEncryptionService {
307
323
  const base64Pattern = /^[A-Za-z0-9+/=]+$/;
308
324
 
309
325
  // All parts should be base64-encoded
310
- if (!parts.every(part => base64Pattern.test(part))) {
326
+ if (!parts.every((part) => base64Pattern.test(part))) {
311
327
  return false;
312
328
  }
313
329