@friggframework/core 2.0.0-next.45 → 2.0.0-next.47

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 (163) hide show
  1. package/README.md +28 -0
  2. package/application/commands/integration-commands.js +19 -0
  3. package/core/Worker.js +8 -21
  4. package/credential/repositories/credential-repository-mongo.js +14 -8
  5. package/credential/repositories/credential-repository-postgres.js +14 -8
  6. package/credential/repositories/credential-repository.js +3 -8
  7. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  8. package/database/adapters/lambda-invoker.js +97 -0
  9. package/database/config.js +11 -2
  10. package/database/models/WebsocketConnection.js +11 -10
  11. package/database/prisma.js +63 -3
  12. package/database/repositories/health-check-repository-mongodb.js +3 -0
  13. package/database/repositories/migration-status-repository-s3.js +137 -0
  14. package/database/use-cases/check-database-state-use-case.js +81 -0
  15. package/database/use-cases/check-encryption-health-use-case.js +3 -2
  16. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  17. package/database/use-cases/get-migration-status-use-case.js +93 -0
  18. package/database/use-cases/run-database-migration-use-case.js +137 -0
  19. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  20. package/database/utils/mongodb-collection-utils.js +91 -0
  21. package/database/utils/mongodb-schema-init.js +106 -0
  22. package/database/utils/prisma-runner.js +400 -0
  23. package/database/utils/prisma-schema-parser.js +182 -0
  24. package/encrypt/Cryptor.js +14 -16
  25. package/generated/prisma-mongodb/client.d.ts +1 -0
  26. package/generated/prisma-mongodb/client.js +4 -0
  27. package/generated/prisma-mongodb/default.d.ts +1 -0
  28. package/generated/prisma-mongodb/default.js +4 -0
  29. package/generated/prisma-mongodb/edge.d.ts +1 -0
  30. package/generated/prisma-mongodb/edge.js +334 -0
  31. package/generated/prisma-mongodb/index-browser.js +316 -0
  32. package/generated/prisma-mongodb/index.d.ts +22897 -0
  33. package/generated/prisma-mongodb/index.js +359 -0
  34. package/generated/prisma-mongodb/package.json +183 -0
  35. package/generated/prisma-mongodb/query-engine-debian-openssl-3.0.x +0 -0
  36. package/generated/prisma-mongodb/query-engine-rhel-openssl-3.0.x +0 -0
  37. package/generated/prisma-mongodb/runtime/binary.d.ts +1 -0
  38. package/generated/prisma-mongodb/runtime/binary.js +289 -0
  39. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  40. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  41. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  42. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  43. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  44. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  45. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  46. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  47. package/generated/prisma-mongodb/schema.prisma +362 -0
  48. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  49. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  50. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  51. package/generated/prisma-mongodb/wasm.js +341 -0
  52. package/generated/prisma-postgresql/client.d.ts +1 -0
  53. package/generated/prisma-postgresql/client.js +4 -0
  54. package/generated/prisma-postgresql/default.d.ts +1 -0
  55. package/generated/prisma-postgresql/default.js +4 -0
  56. package/generated/prisma-postgresql/edge.d.ts +1 -0
  57. package/generated/prisma-postgresql/edge.js +356 -0
  58. package/generated/prisma-postgresql/index-browser.js +338 -0
  59. package/generated/prisma-postgresql/index.d.ts +25071 -0
  60. package/generated/prisma-postgresql/index.js +381 -0
  61. package/generated/prisma-postgresql/package.json +183 -0
  62. package/generated/prisma-postgresql/query-engine-debian-openssl-3.0.x +0 -0
  63. package/generated/prisma-postgresql/query-engine-rhel-openssl-3.0.x +0 -0
  64. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  65. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  66. package/generated/prisma-postgresql/runtime/binary.d.ts +1 -0
  67. package/generated/prisma-postgresql/runtime/binary.js +289 -0
  68. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  69. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  70. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  71. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  72. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  73. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  74. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  75. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  76. package/generated/prisma-postgresql/schema.prisma +345 -0
  77. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  78. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  79. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  80. package/generated/prisma-postgresql/wasm.js +363 -0
  81. package/handlers/database-migration-handler.js +227 -0
  82. package/handlers/routers/auth.js +1 -1
  83. package/handlers/routers/db-migration.handler.js +29 -0
  84. package/handlers/routers/db-migration.js +256 -0
  85. package/handlers/routers/health.js +41 -6
  86. package/handlers/routers/integration-webhook-routers.js +2 -2
  87. package/handlers/use-cases/check-integrations-health-use-case.js +22 -10
  88. package/handlers/workers/db-migration.js +352 -0
  89. package/index.js +12 -0
  90. package/integrations/integration-router.js +60 -70
  91. package/integrations/repositories/integration-repository-interface.js +12 -0
  92. package/integrations/repositories/integration-repository-mongo.js +32 -0
  93. package/integrations/repositories/integration-repository-postgres.js +33 -0
  94. package/integrations/repositories/process-repository-postgres.js +2 -2
  95. package/integrations/tests/doubles/test-integration-repository.js +2 -2
  96. package/logs/logger.js +0 -4
  97. package/modules/entity.js +0 -1
  98. package/modules/repositories/module-repository-mongo.js +3 -12
  99. package/modules/repositories/module-repository-postgres.js +0 -11
  100. package/modules/repositories/module-repository.js +1 -12
  101. package/modules/use-cases/get-entity-options-by-id.js +1 -1
  102. package/modules/use-cases/get-module.js +1 -2
  103. package/modules/use-cases/refresh-entity-options.js +1 -1
  104. package/modules/use-cases/test-module-auth.js +1 -1
  105. package/package.json +82 -66
  106. package/prisma-mongodb/schema.prisma +21 -21
  107. package/prisma-postgresql/schema.prisma +15 -15
  108. package/queues/queuer-util.js +24 -21
  109. package/types/core/index.d.ts +2 -2
  110. package/types/module-plugin/index.d.ts +0 -2
  111. package/user/use-cases/authenticate-user.js +127 -0
  112. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  113. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  114. package/user/use-cases/get-user-from-x-frigg-headers.js +106 -0
  115. package/user/user.js +16 -0
  116. package/websocket/repositories/websocket-connection-repository-mongo.js +11 -10
  117. package/websocket/repositories/websocket-connection-repository-postgres.js +11 -10
  118. package/websocket/repositories/websocket-connection-repository.js +11 -10
  119. package/application/commands/integration-commands.test.js +0 -123
  120. package/database/encryption/encryption-integration.test.js +0 -553
  121. package/database/encryption/encryption-schema-registry.test.js +0 -392
  122. package/database/encryption/field-encryption-service.test.js +0 -525
  123. package/database/encryption/mongo-decryption-fix-verification.test.js +0 -348
  124. package/database/encryption/postgres-decryption-fix-verification.test.js +0 -371
  125. package/database/encryption/postgres-relation-decryption.test.js +0 -245
  126. package/database/encryption/prisma-encryption-extension.test.js +0 -439
  127. package/errors/base-error.test.js +0 -32
  128. package/errors/fetch-error.test.js +0 -79
  129. package/errors/halt-error.test.js +0 -11
  130. package/errors/validation-errors.test.js +0 -120
  131. package/handlers/auth-flow.integration.test.js +0 -147
  132. package/handlers/integration-event-dispatcher.test.js +0 -209
  133. package/handlers/routers/health.test.js +0 -210
  134. package/handlers/routers/integration-webhook-routers.test.js +0 -126
  135. package/handlers/webhook-flow.integration.test.js +0 -356
  136. package/handlers/workers/integration-defined-workers.test.js +0 -184
  137. package/integrations/tests/use-cases/create-integration.test.js +0 -131
  138. package/integrations/tests/use-cases/delete-integration-for-user.test.js +0 -150
  139. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +0 -92
  140. package/integrations/tests/use-cases/get-integration-for-user.test.js +0 -150
  141. package/integrations/tests/use-cases/get-integration-instance.test.js +0 -176
  142. package/integrations/tests/use-cases/get-integrations-for-user.test.js +0 -176
  143. package/integrations/tests/use-cases/get-possible-integrations.test.js +0 -188
  144. package/integrations/tests/use-cases/update-integration-messages.test.js +0 -142
  145. package/integrations/tests/use-cases/update-integration-status.test.js +0 -103
  146. package/integrations/tests/use-cases/update-integration.test.js +0 -141
  147. package/integrations/use-cases/create-process.test.js +0 -178
  148. package/integrations/use-cases/get-process.test.js +0 -190
  149. package/integrations/use-cases/load-integration-context-full.test.js +0 -329
  150. package/integrations/use-cases/load-integration-context.test.js +0 -114
  151. package/integrations/use-cases/update-process-metrics.test.js +0 -308
  152. package/integrations/use-cases/update-process-state.test.js +0 -256
  153. package/lambda/TimeoutCatcher.test.js +0 -68
  154. package/logs/logger.test.js +0 -76
  155. package/modules/module-hydration.test.js +0 -205
  156. package/modules/requester/requester.test.js +0 -28
  157. package/user/tests/use-cases/create-individual-user.test.js +0 -24
  158. package/user/tests/use-cases/create-organization-user.test.js +0 -28
  159. package/user/tests/use-cases/create-token-for-user-id.test.js +0 -19
  160. package/user/tests/use-cases/get-user-from-bearer-token.test.js +0 -64
  161. package/user/tests/use-cases/login-user.test.js +0 -220
  162. package/user/tests/user-password-encryption-isolation.test.js +0 -237
  163. package/user/tests/user-password-hashing.test.js +0 -235
package/README.md CHANGED
@@ -62,6 +62,34 @@ npm install @friggframework/core
62
62
  yarn add @friggframework/core
63
63
  ```
64
64
 
65
+ ### Prisma Support (Optional)
66
+
67
+ `@friggframework/core` supports both MongoDB and PostgreSQL via Prisma ORM. **Prisma is an optional peer dependency** - you only need to install it if you're using database features that require migrations or schema generation.
68
+
69
+ **When you need Prisma:**
70
+ - Running database migrations (`prisma migrate`, `prisma db push`)
71
+ - Generating Prisma clients for your application
72
+ - Using the migration Lambda function (`dbMigrate`)
73
+
74
+ **Installation:**
75
+ ```bash
76
+ # Install Prisma CLI and Client as dev dependencies
77
+ npm install --save-dev prisma @prisma/client
78
+
79
+ # Or with yarn
80
+ yarn add -D prisma @prisma/client
81
+ ```
82
+
83
+ **Generate Prisma Clients:**
84
+ ```bash
85
+ # From @friggframework/core directory
86
+ npm run prisma:generate:mongo # MongoDB only
87
+ npm run prisma:generate:postgres # PostgreSQL only
88
+ npm run prisma:generate # Both databases
89
+ ```
90
+
91
+ **Note:** The published npm package includes pre-generated Prisma clients, so you don't need to install Prisma just to use `@friggframework/core` in production. Prisma is only required if you're actively developing migrations or running the migration Lambda function.
92
+
65
93
  ### Prerequisites
66
94
 
67
95
  - Node.js 16+
@@ -142,6 +142,25 @@ function createIntegrationCommands({ integrationClass } = {}) {
142
142
  return mapErrorToResponse(error);
143
143
  }
144
144
  },
145
+
146
+ /**
147
+ * Update integration configuration
148
+ * @param {Object} params
149
+ * @param {string} params.integrationId - Integration ID
150
+ * @param {Object} params.config - Updated config object
151
+ * @returns {Promise<Object>} Updated integration
152
+ */
153
+ async updateIntegrationConfig({ integrationId, config }) {
154
+ try {
155
+ const integration = await integrationRepository.updateIntegrationConfig(
156
+ integrationId,
157
+ config
158
+ );
159
+ return integration;
160
+ } catch (error) {
161
+ return mapErrorToResponse(error);
162
+ }
163
+ },
145
164
  };
146
165
  }
147
166
 
package/core/Worker.js CHANGED
@@ -1,10 +1,9 @@
1
- const AWS = require('aws-sdk');
1
+ const { SQSClient, GetQueueUrlCommand, SendMessageCommand } = require('@aws-sdk/client-sqs');
2
2
  const _ = require('lodash');
3
3
  const { RequiredPropertyError } = require('../errors');
4
4
  const { get } = require('../assertions');
5
5
 
6
- AWS.config.update({ region: process.env.AWS_REGION });
7
- const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
6
+ const sqs = new SQSClient({ region: process.env.AWS_REGION });
8
7
 
9
8
  class Worker {
10
9
  async getQueueURL(params) {
@@ -12,15 +11,9 @@ class Worker {
12
11
  // let params = {
13
12
  // QueueName: process.env.QueueName
14
13
  // };
15
- return new Promise((resolve, reject) => {
16
- sqs.getQueueUrl(params, (err, data) => {
17
- if (err) {
18
- reject(err);
19
- } else {
20
- resolve(data.QueueUrl);
21
- }
22
- });
23
- });
14
+ const command = new GetQueueUrlCommand(params);
15
+ const data = await sqs.send(command);
16
+ return data.QueueUrl;
24
17
  }
25
18
 
26
19
  async run(params, context = {}) {
@@ -54,15 +47,9 @@ class Worker {
54
47
  }
55
48
 
56
49
  async sendAsyncSQSMessage(params) {
57
- return new Promise((resolve, reject) => {
58
- sqs.sendMessage(params, (err, data) => {
59
- if (err) {
60
- reject(err);
61
- } else {
62
- resolve(data.MessageId);
63
- }
64
- });
65
- });
50
+ const command = new SendMessageCommand(params);
51
+ const data = await sqs.send(command);
52
+ return data.MessageId;
66
53
  }
67
54
 
68
55
  // Throw an exception if the params do not validate
@@ -44,7 +44,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
44
44
  userId: credential.userId,
45
45
  externalId: credential.externalId,
46
46
  authIsValid: credential.authIsValid,
47
- subType: credential.subType,
48
47
  ...data, // Spread OAuth tokens from JSON field
49
48
  };
50
49
  }
@@ -100,6 +99,17 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
100
99
  if (!identifiers)
101
100
  throw new Error('identifiers required to upsert credential');
102
101
 
102
+ if (!identifiers.user && !identifiers.userId) {
103
+ throw new Error('user or userId required in identifiers');
104
+ }
105
+ if (!identifiers.externalId) {
106
+ throw new Error(
107
+ 'externalId required in identifiers to prevent credential collision. ' +
108
+ 'When multiple credentials exist for the same user, both userId and externalId ' +
109
+ 'are needed to uniquely identify which credential to update.'
110
+ );
111
+ }
112
+
103
113
  // Build where clause from identifiers
104
114
  const where = this._convertIdentifiersToWhere(identifiers);
105
115
 
@@ -109,7 +119,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
109
119
  userId,
110
120
  externalId,
111
121
  authIsValid,
112
- subType,
122
+
113
123
  ...oauthData
114
124
  } = details;
115
125
 
@@ -132,7 +142,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
132
142
  authIsValid !== undefined
133
143
  ? authIsValid
134
144
  : existing.authIsValid,
135
- subType: subType !== undefined ? subType : existing.subType,
136
145
  data: mergedData,
137
146
  },
138
147
  });
@@ -152,7 +161,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
152
161
  userId: userId || user,
153
162
  externalId,
154
163
  authIsValid: authIsValid,
155
- subType,
164
+
156
165
  data: oauthData,
157
166
  },
158
167
  });
@@ -225,7 +234,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
225
234
  userId,
226
235
  externalId,
227
236
  authIsValid,
228
- subType,
237
+
229
238
  ...oauthData
230
239
  } = updates;
231
240
 
@@ -240,7 +249,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
240
249
  externalId !== undefined ? externalId : existing.externalId,
241
250
  authIsValid:
242
251
  authIsValid !== undefined ? authIsValid : existing.authIsValid,
243
- subType: subType !== undefined ? subType : existing.subType,
244
252
  data: mergedData,
245
253
  },
246
254
  });
@@ -273,7 +281,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
273
281
  if (identifiers.user) where.userId = identifiers.user;
274
282
  if (identifiers.userId) where.userId = identifiers.userId;
275
283
  if (identifiers.externalId) where.externalId = identifiers.externalId;
276
- if (identifiers.subType) where.subType = identifiers.subType;
277
284
 
278
285
  return where;
279
286
  }
@@ -292,7 +299,6 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
292
299
  if (filter.user) where.userId = filter.user;
293
300
  if (filter.userId) where.userId = filter.userId;
294
301
  if (filter.externalId) where.externalId = filter.externalId;
295
- if (filter.subType) where.subType = filter.subType;
296
302
 
297
303
  return where;
298
304
  }
@@ -61,7 +61,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
61
61
  userId: credential.userId?.toString(),
62
62
  externalId: credential.externalId,
63
63
  authIsValid: credential.authIsValid,
64
- subType: credential.subType,
65
64
  ...data, // Spread OAuth tokens from JSON field
66
65
  };
67
66
  }
@@ -119,12 +118,23 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
119
118
  if (!identifiers)
120
119
  throw new Error('identifiers required to upsert credential');
121
120
 
121
+ if (!identifiers.user && !identifiers.userId) {
122
+ throw new Error('user or userId required in identifiers');
123
+ }
124
+ if (!identifiers.externalId) {
125
+ throw new Error(
126
+ 'externalId required in identifiers to prevent credential collision. ' +
127
+ 'When multiple credentials exist for the same user, both userId and externalId ' +
128
+ 'are needed to uniquely identify which credential to update.'
129
+ );
130
+ }
131
+
122
132
  const where = this._convertIdentifiersToWhere(identifiers);
123
133
 
124
134
  const { user, externalId } = identifiers;
125
135
 
126
136
  // Separate schema fields from dynamic OAuth data
127
- const { authIsValid, subType, ...oauthData } = details;
137
+ const { authIsValid, ...oauthData } = details;
128
138
 
129
139
  const existing = await this.prisma.credential.findFirst({ where });
130
140
 
@@ -143,7 +153,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
143
153
  authIsValid !== undefined
144
154
  ? authIsValid
145
155
  : existing.authIsValid,
146
- subType: subType !== undefined ? subType : existing.subType,
147
156
  data: mergedData,
148
157
  },
149
158
  });
@@ -162,7 +171,7 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
162
171
  userId: this._convertId(user),
163
172
  externalId,
164
173
  authIsValid: authIsValid,
165
- subType,
174
+
166
175
  data: oauthData,
167
176
  },
168
177
  });
@@ -231,7 +240,7 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
231
240
  }
232
241
 
233
242
  // Separate schema fields from OAuth data
234
- const { user, authIsValid, subType, ...oauthData } =
243
+ const { user, authIsValid, ...oauthData } =
235
244
  updates;
236
245
 
237
246
  // Merge OAuth data with existing
@@ -245,7 +254,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
245
254
  externalId !== undefined ? externalId : existing.externalId,
246
255
  authIsValid:
247
256
  authIsValid !== undefined ? authIsValid : existing.authIsValid,
248
- subType: subType !== undefined ? subType : existing.subType,
249
257
  data: mergedData,
250
258
  },
251
259
  });
@@ -278,7 +286,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
278
286
  if (identifiers.userId)
279
287
  where.userId = this._convertId(identifiers.userId);
280
288
  if (identifiers.externalId) where.externalId = identifiers.externalId;
281
- if (identifiers.subType) where.subType = identifiers.subType;
282
289
 
283
290
  return where;
284
291
  }
@@ -298,7 +305,6 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
298
305
  if (filter.user) where.userId = this._convertId(filter.user);
299
306
  if (filter.userId) where.userId = this._convertId(filter.userId);
300
307
  if (filter.externalId) where.externalId = filter.externalId;
301
- if (filter.subType) where.subType = filter.subType;
302
308
 
303
309
  return where;
304
310
  }
@@ -50,7 +50,6 @@ class CredentialRepository extends CredentialRepositoryInterface {
50
50
  userId: credential.userId,
51
51
  externalId: credential.externalId,
52
52
  authIsValid: credential.authIsValid,
53
- subType: credential.subType,
54
53
  ...data, // Spread OAuth tokens from JSON field
55
54
  };
56
55
  }
@@ -115,7 +114,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
115
114
  userId,
116
115
  externalId,
117
116
  authIsValid,
118
- subType,
117
+
119
118
  ...oauthData
120
119
  } = details;
121
120
 
@@ -138,7 +137,6 @@ class CredentialRepository extends CredentialRepositoryInterface {
138
137
  authIsValid !== undefined
139
138
  ? authIsValid
140
139
  : existing.authIsValid,
141
- subType: subType !== undefined ? subType : existing.subType,
142
140
  data: mergedData,
143
141
  },
144
142
  });
@@ -158,7 +156,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
158
156
  userId: userId || user,
159
157
  externalId,
160
158
  authIsValid: authIsValid,
161
- subType,
159
+
162
160
  data: oauthData,
163
161
  },
164
162
  });
@@ -231,7 +229,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
231
229
  userId,
232
230
  externalId,
233
231
  authIsValid,
234
- subType,
232
+
235
233
  ...oauthData
236
234
  } = updates;
237
235
 
@@ -246,7 +244,6 @@ class CredentialRepository extends CredentialRepositoryInterface {
246
244
  externalId !== undefined ? externalId : existing.externalId,
247
245
  authIsValid:
248
246
  authIsValid !== undefined ? authIsValid : existing.authIsValid,
249
- subType: subType !== undefined ? subType : existing.subType,
250
247
  data: mergedData,
251
248
  },
252
249
  });
@@ -279,7 +276,6 @@ class CredentialRepository extends CredentialRepositoryInterface {
279
276
  if (identifiers.user) where.userId = identifiers.user;
280
277
  if (identifiers.userId) where.userId = identifiers.userId;
281
278
  if (identifiers.externalId) where.externalId = identifiers.externalId;
282
- if (identifiers.subType) where.subType = identifiers.subType;
283
279
 
284
280
  return where;
285
281
  }
@@ -298,7 +294,6 @@ class CredentialRepository extends CredentialRepositoryInterface {
298
294
  if (filter.user) where.userId = filter.user;
299
295
  if (filter.userId) where.userId = filter.userId;
300
296
  if (filter.externalId) where.externalId = filter.externalId;
301
- if (filter.subType) where.subType = filter.subType;
302
297
 
303
298
  return where;
304
299
  }
@@ -0,0 +1,198 @@
1
+ # MongoDB Transaction Namespace Fix
2
+
3
+ ## Problem
4
+
5
+ The encryption health check was failing with the following error:
6
+
7
+ ```
8
+ Cannot create namespace frigg.Credential in multi-document transaction.
9
+ Error code: 263
10
+ ```
11
+
12
+ ### Root Cause
13
+
14
+ MongoDB does not allow creating collections (namespaces) inside multi-document transactions. When Prisma tries to create a document in a collection that doesn't exist yet, MongoDB needs to implicitly create the collection. If this happens inside a transaction context, MongoDB throws error code 263.
15
+
16
+ ### Technical Details
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
21
+
22
+ ## Solution
23
+
24
+ **Implemented a comprehensive schema initialization system that ensures all collections exist at application startup.**
25
+
26
+ ### Architectural Approach
27
+
28
+ Rather than checking before each individual database operation, we take a **systematic, fail-fast approach**:
29
+
30
+ 1. **Parse Prisma Schema**: Extract all collection names from the Prisma schema definition
31
+ 2. **Initialize at Startup**: Create all collections when the database connection is established
32
+ 3. **Fail Fast**: If there are database issues, the application fails immediately at startup rather than during runtime operations
33
+ 4. **Idempotent**: Safe to run multiple times - only creates collections that don't exist
34
+
35
+ This follows the **"fail fast"** principle and ensures consistent state across all application instances.
36
+
37
+ ### Changes Made
38
+
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
45
+
46
+ 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)
51
+
52
+ 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
+ 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
59
+
60
+ 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
64
+
65
+ ### Implementation Flow
66
+
67
+ ```javascript
68
+ // 1. Application startup - connect to database
69
+ await connectPrisma();
70
+ └─> await initializeMongoDBSchema();
71
+ └─> await ensureCollectionsExist([
72
+ 'User', 'Token', 'Credential', 'Entity',
73
+ 'Integration', 'IntegrationMapping', 'Process',
74
+ 'Sync', 'DataIdentifier', 'Association',
75
+ 'AssociationObject', 'State', 'WebsocketConnection'
76
+ ]);
77
+
78
+ // 2. Now all collections exist - safe to handle requests
79
+ // No per-operation checks needed!
80
+ await prisma.credential.create({ data: {...} }); // Works without namespace error
81
+ ```
82
+
83
+ ## Best Practices Followed
84
+
85
+ 1. **Domain-Driven Design**: Created reusable utility module for MongoDB-specific concerns
86
+ 2. **Hexagonal Architecture**: Infrastructure concerns (schema initialization) handled in infrastructure layer
87
+ 3. **Test-Driven Development**: Added comprehensive tests for all utility functions
88
+ 4. **Fail Fast Principle**: Database issues discovered at startup, not during runtime
89
+ 5. **Idempotency**: Safe to run multiple times across multiple instances
90
+ 6. **Error Handling**: Graceful degradation on race conditions and errors
91
+ 7. **Documentation**: Inline comments, JSDoc, and comprehensive documentation
92
+
93
+ ## Benefits
94
+
95
+ ### 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
101
+
102
+ ### 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
+ ### 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
113
+
114
+ ## Design Decisions
115
+
116
+ ### Why Initialize at Startup?
117
+
118
+ We considered two approaches:
119
+
120
+ **❌ Per-Operation Checks (Initial approach)**
121
+ ```javascript
122
+ async createCredential(data) {
123
+ await ensureCollectionExists('Credential'); // Check every time
124
+ return await prisma.credential.create({ data });
125
+ }
126
+ ```
127
+ - Pros: Guarantees collection exists before each operation
128
+ - Cons: Runtime overhead, repeated checks, scattered logic
129
+
130
+ **✅ Startup Initialization (Final approach)**
131
+ ```javascript
132
+ // Once at startup
133
+ await connectPrisma(); // Initializes all collections
134
+
135
+ // All operations just work
136
+ async createCredential(data) {
137
+ return await prisma.credential.create({ data }); // No checks needed
138
+ }
139
+ ```
140
+ - Pros: Zero runtime overhead, centralized logic, fail fast, consistent
141
+ - Cons: Requires database connection at startup (already required)
142
+
143
+ ### Benefits of Startup Approach
144
+
145
+ 1. **Performance**: Collections created once vs. checking before every operation
146
+ 2. **Simplicity**: No conditional logic in repositories
147
+ 3. **Reliability**: Fail fast at startup if database has issues
148
+ 4. **Maintainability**: Single source of truth for schema initialization
149
+ 5. **DDD Alignment**: Infrastructure concerns handled in infrastructure layer
150
+
151
+ ## Logging Output
152
+
153
+ When the application starts, you'll see clear logging:
154
+
155
+ ```
156
+ Initializing MongoDB schema - ensuring all collections exist...
157
+ Created MongoDB collection: Credential
158
+ MongoDB schema initialization complete - 13 collections verified (45ms)
159
+ ```
160
+
161
+ On subsequent startups (collections already exist):
162
+ ```
163
+ Initializing MongoDB schema - ensuring all collections exist...
164
+ MongoDB schema initialization complete - 13 collections verified (12ms)
165
+ ```
166
+
167
+ ## References
168
+
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
173
+
174
+ ## Future Considerations
175
+
176
+ ### Automatic Schema Sync
177
+ 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
181
+
182
+ ### Migration Support
183
+ 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
187
+
188
+ ### Multi-Database Support
189
+ 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
193
+
194
+ ### Index Creation
195
+ 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
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Lambda Invoker Adapter
3
+ * Infrastructure layer - handles AWS Lambda function invocations
4
+ *
5
+ * Part of Hexagonal Architecture:
6
+ * - Infrastructure Layer adapter for AWS SDK
7
+ * - Used by Domain Layer use cases
8
+ * - Isolates AWS-specific logic from business logic
9
+ */
10
+
11
+ const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
12
+
13
+ /**
14
+ * Custom error for Lambda invocation failures
15
+ * Provides structured error information for debugging
16
+ */
17
+ class LambdaInvocationError extends Error {
18
+ constructor(message, functionName, statusCode) {
19
+ super(message);
20
+ this.name = 'LambdaInvocationError';
21
+ this.functionName = functionName;
22
+ this.statusCode = statusCode;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Adapter for invoking AWS Lambda functions
28
+ *
29
+ * Infrastructure layer - handles AWS SDK communication
30
+ * Converts AWS SDK responses to domain-friendly formats
31
+ */
32
+ class LambdaInvoker {
33
+ /**
34
+ * @param {LambdaClient} lambdaClient - AWS Lambda client (injected for testability)
35
+ */
36
+ constructor(lambdaClient = new LambdaClient({})) {
37
+ this.client = lambdaClient;
38
+ }
39
+
40
+ /**
41
+ * Invoke Lambda function synchronously
42
+ *
43
+ * @param {string} functionName - Lambda function name or ARN
44
+ * @param {Object} payload - Event payload to send to Lambda
45
+ * @returns {Promise<Object>} Parsed response body
46
+ * @throws {LambdaInvocationError} If Lambda returns error status
47
+ * @throws {Error} If AWS SDK call fails
48
+ */
49
+ async invoke(functionName, payload) {
50
+ try {
51
+ const command = new InvokeCommand({
52
+ FunctionName: functionName,
53
+ InvocationType: 'RequestResponse', // Synchronous
54
+ Payload: JSON.stringify(payload),
55
+ });
56
+
57
+ const response = await this.client.send(command);
58
+
59
+ // Parse response payload
60
+ let result;
61
+ try {
62
+ result = JSON.parse(Buffer.from(response.Payload).toString());
63
+ } catch (parseError) {
64
+ throw new LambdaInvocationError(
65
+ `Failed to parse Lambda response: ${parseError.message}`,
66
+ functionName,
67
+ null
68
+ );
69
+ }
70
+
71
+ // Check status code
72
+ if (result.statusCode === 200) {
73
+ return result.body;
74
+ }
75
+
76
+ // Lambda returned error status
77
+ const errorMessage = result.body?.error || 'Lambda invocation failed';
78
+ throw new LambdaInvocationError(
79
+ `Lambda ${functionName} returned error: ${errorMessage}`,
80
+ functionName,
81
+ result.statusCode
82
+ );
83
+ } catch (error) {
84
+ // Re-throw LambdaInvocationError as-is
85
+ if (error instanceof LambdaInvocationError) {
86
+ throw error;
87
+ }
88
+
89
+ // Wrap AWS SDK errors
90
+ throw new Error(`Failed to invoke Lambda ${functionName}: ${error.message}`);
91
+ }
92
+ }
93
+ }
94
+
95
+ module.exports = { LambdaInvoker, LambdaInvocationError };
96
+
97
+
@@ -4,13 +4,22 @@
4
4
  */
5
5
 
6
6
  /**
7
- * Determines database type from app definition
8
- * Reads backend/index.js Definition.database configuration
7
+ * Determines database type from environment or app definition
8
+ *
9
+ * Detection order:
10
+ * 1. DB_TYPE environment variable (set for migration handlers)
11
+ * 2. App definition (backend/index.js Definition.database configuration)
9
12
  *
10
13
  * @returns {'mongodb'|'postgresql'} Database type
11
14
  * @throws {Error} If database type cannot be determined or app definition missing
12
15
  */
13
16
  function getDatabaseType() {
17
+ // First, check DB_TYPE environment variable (migration handlers set this)
18
+ if (process.env.DB_TYPE) {
19
+ return process.env.DB_TYPE;
20
+ }
21
+
22
+ // Fallback: Load app definition
14
23
  try {
15
24
  const path = require('node:path');
16
25
  const fs = require('node:fs');