@friggframework/core 2.0.0-next.9 → 2.0.0-next.90

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 (309) hide show
  1. package/CLAUDE.md +702 -0
  2. package/README.md +959 -50
  3. package/application/commands/README.md +451 -0
  4. package/application/commands/credential-commands.js +245 -0
  5. package/application/commands/entity-commands.js +336 -0
  6. package/application/commands/integration-commands.js +271 -0
  7. package/application/commands/scheduler-commands.js +263 -0
  8. package/application/commands/user-commands.js +283 -0
  9. package/application/index.js +73 -0
  10. package/assertions/index.js +0 -3
  11. package/core/CLAUDE.md +690 -0
  12. package/core/Worker.js +60 -24
  13. package/core/create-handler.js +84 -6
  14. package/credential/repositories/credential-repository-documentdb.js +304 -0
  15. package/credential/repositories/credential-repository-factory.js +54 -0
  16. package/credential/repositories/credential-repository-interface.js +98 -0
  17. package/credential/repositories/credential-repository-mongo.js +269 -0
  18. package/credential/repositories/credential-repository-postgres.js +287 -0
  19. package/credential/repositories/credential-repository.js +300 -0
  20. package/credential/use-cases/get-credential-for-user.js +25 -0
  21. package/credential/use-cases/update-authentication-status.js +15 -0
  22. package/database/MONGODB_TRANSACTION_FIX.md +198 -0
  23. package/database/adapters/lambda-invoker.js +97 -0
  24. package/database/config.js +154 -0
  25. package/database/documentdb-encryption-service.js +330 -0
  26. package/database/documentdb-utils.js +136 -0
  27. package/database/encryption/README.md +839 -0
  28. package/database/encryption/documentdb-encryption-service.md +3575 -0
  29. package/database/encryption/encryption-schema-registry.js +401 -0
  30. package/database/encryption/field-encryption-service.js +254 -0
  31. package/database/encryption/logger.js +79 -0
  32. package/database/encryption/prisma-encryption-extension.js +230 -0
  33. package/database/index.js +21 -21
  34. package/database/prisma.js +182 -0
  35. package/database/repositories/health-check-repository-documentdb.js +138 -0
  36. package/database/repositories/health-check-repository-factory.js +48 -0
  37. package/database/repositories/health-check-repository-interface.js +82 -0
  38. package/database/repositories/health-check-repository-mongodb.js +89 -0
  39. package/database/repositories/health-check-repository-postgres.js +82 -0
  40. package/database/repositories/migration-status-repository-s3.js +137 -0
  41. package/database/use-cases/check-database-health-use-case.js +29 -0
  42. package/database/use-cases/check-database-state-use-case.js +81 -0
  43. package/database/use-cases/check-encryption-health-use-case.js +83 -0
  44. package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
  45. package/database/use-cases/get-migration-status-use-case.js +93 -0
  46. package/database/use-cases/run-database-migration-use-case.js +139 -0
  47. package/database/use-cases/test-encryption-use-case.js +253 -0
  48. package/database/use-cases/trigger-database-migration-use-case.js +157 -0
  49. package/database/utils/mongodb-collection-utils.js +94 -0
  50. package/database/utils/mongodb-schema-init.js +108 -0
  51. package/database/utils/prisma-runner.js +477 -0
  52. package/database/utils/prisma-schema-parser.js +182 -0
  53. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
  54. package/encrypt/Cryptor.js +34 -168
  55. package/encrypt/index.js +1 -2
  56. package/errors/client-safe-error.js +26 -0
  57. package/errors/fetch-error.js +15 -7
  58. package/errors/index.js +2 -0
  59. package/generated/prisma-mongodb/client.d.ts +1 -0
  60. package/generated/prisma-mongodb/client.js +4 -0
  61. package/generated/prisma-mongodb/default.d.ts +1 -0
  62. package/generated/prisma-mongodb/default.js +4 -0
  63. package/generated/prisma-mongodb/edge.d.ts +1 -0
  64. package/generated/prisma-mongodb/edge.js +335 -0
  65. package/generated/prisma-mongodb/index-browser.js +317 -0
  66. package/generated/prisma-mongodb/index.d.ts +22955 -0
  67. package/generated/prisma-mongodb/index.js +360 -0
  68. package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  69. package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  70. package/generated/prisma-mongodb/package.json +183 -0
  71. package/generated/prisma-mongodb/runtime/edge-esm.js +34 -0
  72. package/generated/prisma-mongodb/runtime/edge.js +34 -0
  73. package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
  74. package/generated/prisma-mongodb/runtime/index-browser.js +16 -0
  75. package/generated/prisma-mongodb/runtime/library.d.ts +3977 -0
  76. package/generated/prisma-mongodb/runtime/library.js +146 -0
  77. package/generated/prisma-mongodb/runtime/react-native.js +83 -0
  78. package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +84 -0
  79. package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +36 -0
  80. package/generated/prisma-mongodb/schema.prisma +368 -0
  81. package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +4 -0
  82. package/generated/prisma-mongodb/wasm-worker-loader.mjs +4 -0
  83. package/generated/prisma-mongodb/wasm.d.ts +1 -0
  84. package/generated/prisma-mongodb/wasm.js +342 -0
  85. package/generated/prisma-postgresql/client.d.ts +1 -0
  86. package/generated/prisma-postgresql/client.js +4 -0
  87. package/generated/prisma-postgresql/default.d.ts +1 -0
  88. package/generated/prisma-postgresql/default.js +4 -0
  89. package/generated/prisma-postgresql/edge.d.ts +1 -0
  90. package/generated/prisma-postgresql/edge.js +357 -0
  91. package/generated/prisma-postgresql/index-browser.js +339 -0
  92. package/generated/prisma-postgresql/index.d.ts +25135 -0
  93. package/generated/prisma-postgresql/index.js +382 -0
  94. package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
  95. package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
  96. package/generated/prisma-postgresql/package.json +183 -0
  97. package/generated/prisma-postgresql/query_engine_bg.js +2 -0
  98. package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
  99. package/generated/prisma-postgresql/runtime/edge-esm.js +34 -0
  100. package/generated/prisma-postgresql/runtime/edge.js +34 -0
  101. package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
  102. package/generated/prisma-postgresql/runtime/index-browser.js +16 -0
  103. package/generated/prisma-postgresql/runtime/library.d.ts +3977 -0
  104. package/generated/prisma-postgresql/runtime/library.js +146 -0
  105. package/generated/prisma-postgresql/runtime/react-native.js +83 -0
  106. package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +84 -0
  107. package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +36 -0
  108. package/generated/prisma-postgresql/schema.prisma +351 -0
  109. package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +4 -0
  110. package/generated/prisma-postgresql/wasm-worker-loader.mjs +4 -0
  111. package/generated/prisma-postgresql/wasm.d.ts +1 -0
  112. package/generated/prisma-postgresql/wasm.js +364 -0
  113. package/handlers/WEBHOOKS.md +653 -0
  114. package/handlers/app-definition-loader.js +38 -0
  115. package/handlers/app-handler-helpers.js +57 -0
  116. package/handlers/backend-utils.js +297 -0
  117. package/handlers/database-migration-handler.js +227 -0
  118. package/handlers/integration-event-dispatcher.js +54 -0
  119. package/handlers/routers/HEALTHCHECK.md +342 -0
  120. package/handlers/routers/auth.js +15 -0
  121. package/handlers/routers/db-migration.handler.js +29 -0
  122. package/handlers/routers/db-migration.js +326 -0
  123. package/handlers/routers/health.js +518 -0
  124. package/handlers/routers/integration-defined-routers.js +117 -0
  125. package/handlers/routers/integration-webhook-routers.js +67 -0
  126. package/handlers/routers/user.js +63 -0
  127. package/handlers/routers/websocket.js +57 -0
  128. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  129. package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
  130. package/handlers/workers/db-migration.js +352 -0
  131. package/handlers/workers/dlq-processor.js +63 -0
  132. package/handlers/workers/integration-defined-workers.js +30 -0
  133. package/index.js +82 -46
  134. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -0
  135. package/infrastructure/scheduler/index.js +33 -0
  136. package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
  137. package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
  138. package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
  139. package/integrations/EXTENSIONS.md +240 -0
  140. package/integrations/WEBHOOK-QUICKSTART.md +151 -0
  141. package/integrations/extension.js +254 -0
  142. package/integrations/index.js +20 -10
  143. package/integrations/integration-base.js +487 -55
  144. package/integrations/integration-router.js +396 -179
  145. package/integrations/options.js +1 -1
  146. package/integrations/repositories/integration-mapping-repository-documentdb.js +280 -0
  147. package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
  148. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  149. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  150. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  151. package/integrations/repositories/integration-mapping-repository.js +156 -0
  152. package/integrations/repositories/integration-repository-documentdb.js +219 -0
  153. package/integrations/repositories/integration-repository-factory.js +51 -0
  154. package/integrations/repositories/integration-repository-interface.js +144 -0
  155. package/integrations/repositories/integration-repository-mongo.js +330 -0
  156. package/integrations/repositories/integration-repository-postgres.js +385 -0
  157. package/integrations/repositories/process-repository-documentdb.js +311 -0
  158. package/integrations/repositories/process-repository-factory.js +53 -0
  159. package/integrations/repositories/process-repository-interface.js +136 -0
  160. package/integrations/repositories/process-repository-mongo.js +262 -0
  161. package/integrations/repositories/process-repository-postgres.js +380 -0
  162. package/integrations/repositories/process-update-ops-shared.js +112 -0
  163. package/integrations/tests/doubles/config-capturing-integration.js +81 -0
  164. package/integrations/tests/doubles/dummy-integration-class.js +105 -0
  165. package/integrations/tests/doubles/test-integration-repository.js +112 -0
  166. package/integrations/use-cases/create-integration.js +83 -0
  167. package/integrations/use-cases/create-process.js +128 -0
  168. package/integrations/use-cases/delete-integration-for-user.js +101 -0
  169. package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
  170. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
  171. package/integrations/use-cases/get-integration-for-user.js +78 -0
  172. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  173. package/integrations/use-cases/get-integration-instance.js +83 -0
  174. package/integrations/use-cases/get-integrations-for-user.js +88 -0
  175. package/integrations/use-cases/get-possible-integrations.js +27 -0
  176. package/integrations/use-cases/get-process.js +87 -0
  177. package/integrations/use-cases/index.js +19 -0
  178. package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
  179. package/integrations/use-cases/load-integration-context.js +71 -0
  180. package/integrations/use-cases/update-integration-messages.js +44 -0
  181. package/integrations/use-cases/update-integration-status.js +32 -0
  182. package/integrations/use-cases/update-integration.js +92 -0
  183. package/integrations/use-cases/update-process-metrics.js +214 -0
  184. package/integrations/use-cases/update-process-state.js +158 -0
  185. package/integrations/utils/map-integration-dto.js +37 -0
  186. package/jest-global-setup-noop.js +3 -0
  187. package/jest-global-teardown-noop.js +3 -0
  188. package/logs/logger.js +0 -4
  189. package/{module-plugin → modules}/index.js +0 -10
  190. package/modules/module-factory.js +56 -0
  191. package/modules/module.js +274 -0
  192. package/modules/repositories/module-repository-documentdb.js +350 -0
  193. package/modules/repositories/module-repository-factory.js +40 -0
  194. package/modules/repositories/module-repository-interface.js +145 -0
  195. package/modules/repositories/module-repository-mongo.js +436 -0
  196. package/modules/repositories/module-repository-postgres.js +481 -0
  197. package/modules/repositories/module-repository.js +369 -0
  198. package/modules/requester/api-key.js +52 -0
  199. package/modules/requester/oauth-2.js +396 -0
  200. package/modules/requester/requester.js +280 -0
  201. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  202. package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
  203. package/modules/tests/doubles/test-module-factory.js +16 -0
  204. package/modules/tests/doubles/test-module-repository.js +39 -0
  205. package/modules/use-cases/get-entities-for-user.js +32 -0
  206. package/modules/use-cases/get-entity-options-by-id.js +71 -0
  207. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  208. package/modules/use-cases/get-module-instance-from-type.js +34 -0
  209. package/modules/use-cases/get-module.js +74 -0
  210. package/modules/use-cases/process-authorization-callback.js +243 -0
  211. package/modules/use-cases/refresh-entity-options.js +72 -0
  212. package/modules/use-cases/test-module-auth.js +72 -0
  213. package/modules/utils/map-module-dto.js +18 -0
  214. package/package.json +82 -50
  215. package/prisma-mongodb/schema.prisma +368 -0
  216. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  217. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  218. package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  219. package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
  220. package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
  221. package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
  222. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  223. package/prisma-postgresql/schema.prisma +351 -0
  224. package/queues/queuer-util.js +103 -21
  225. package/syncs/manager.js +468 -443
  226. package/syncs/repositories/sync-repository-documentdb.js +240 -0
  227. package/syncs/repositories/sync-repository-factory.js +43 -0
  228. package/syncs/repositories/sync-repository-interface.js +109 -0
  229. package/syncs/repositories/sync-repository-mongo.js +239 -0
  230. package/syncs/repositories/sync-repository-postgres.js +319 -0
  231. package/syncs/sync.js +0 -1
  232. package/token/repositories/token-repository-documentdb.js +137 -0
  233. package/token/repositories/token-repository-factory.js +40 -0
  234. package/token/repositories/token-repository-interface.js +131 -0
  235. package/token/repositories/token-repository-mongo.js +219 -0
  236. package/token/repositories/token-repository-postgres.js +264 -0
  237. package/token/repositories/token-repository.js +219 -0
  238. package/types/associations/index.d.ts +0 -17
  239. package/types/core/index.d.ts +12 -4
  240. package/types/database/index.d.ts +10 -2
  241. package/types/encrypt/index.d.ts +5 -3
  242. package/types/integrations/index.d.ts +3 -8
  243. package/types/module-plugin/index.d.ts +17 -69
  244. package/types/syncs/index.d.ts +0 -17
  245. package/user/repositories/user-repository-documentdb.js +441 -0
  246. package/user/repositories/user-repository-factory.js +52 -0
  247. package/user/repositories/user-repository-interface.js +201 -0
  248. package/user/repositories/user-repository-mongo.js +308 -0
  249. package/user/repositories/user-repository-postgres.js +360 -0
  250. package/user/tests/doubles/test-user-repository.js +72 -0
  251. package/user/use-cases/authenticate-user.js +127 -0
  252. package/user/use-cases/authenticate-with-shared-secret.js +48 -0
  253. package/user/use-cases/create-individual-user.js +61 -0
  254. package/user/use-cases/create-organization-user.js +47 -0
  255. package/user/use-cases/create-token-for-user-id.js +30 -0
  256. package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
  257. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  258. package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
  259. package/user/use-cases/login-user.js +122 -0
  260. package/user/user.js +125 -0
  261. package/utils/backend-path.js +38 -0
  262. package/utils/index.js +6 -0
  263. package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
  264. package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
  265. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  266. package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
  267. package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
  268. package/websocket/repositories/websocket-connection-repository.js +161 -0
  269. package/assertions/is-equal.js +0 -17
  270. package/associations/model.js +0 -54
  271. package/database/models/IndividualUser.js +0 -76
  272. package/database/models/OrganizationUser.js +0 -29
  273. package/database/models/State.js +0 -9
  274. package/database/models/Token.js +0 -70
  275. package/database/models/UserModel.js +0 -7
  276. package/database/models/WebsocketConnection.js +0 -49
  277. package/database/mongo.js +0 -45
  278. package/database/mongoose.js +0 -5
  279. package/encrypt/Cryptor.test.js +0 -32
  280. package/encrypt/encrypt.js +0 -132
  281. package/encrypt/encrypt.test.js +0 -1069
  282. package/encrypt/test-encrypt.js +0 -107
  283. package/errors/base-error.test.js +0 -32
  284. package/errors/fetch-error.test.js +0 -79
  285. package/errors/halt-error.test.js +0 -11
  286. package/errors/validation-errors.test.js +0 -120
  287. package/integrations/create-frigg-backend.js +0 -31
  288. package/integrations/integration-factory.js +0 -251
  289. package/integrations/integration-mapping.js +0 -43
  290. package/integrations/integration-model.js +0 -46
  291. package/integrations/integration-user.js +0 -144
  292. package/integrations/test/integration-base.test.js +0 -144
  293. package/lambda/TimeoutCatcher.test.js +0 -68
  294. package/logs/logger.test.js +0 -76
  295. package/module-plugin/auther.js +0 -393
  296. package/module-plugin/credential.js +0 -22
  297. package/module-plugin/entity-manager.js +0 -70
  298. package/module-plugin/entity.js +0 -46
  299. package/module-plugin/manager.js +0 -169
  300. package/module-plugin/module-factory.js +0 -61
  301. package/module-plugin/requester/api-key.js +0 -36
  302. package/module-plugin/requester/oauth-2.js +0 -219
  303. package/module-plugin/requester/requester.js +0 -165
  304. package/module-plugin/requester/requester.test.js +0 -28
  305. package/module-plugin/test/auther.test.js +0 -97
  306. package/syncs/model.js +0 -62
  307. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  308. /package/{module-plugin → modules}/requester/basic.js +0 -0
  309. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Encryption Logger
3
+ *
4
+ * Centralized logging for encryption operations.
5
+ * Prevents sensitive data leakage in production logs.
6
+ */
7
+
8
+ const LOG_LEVELS = {
9
+ DEBUG: 0,
10
+ INFO: 1,
11
+ WARN: 2,
12
+ ERROR: 3,
13
+ };
14
+
15
+ class EncryptionLogger {
16
+ constructor() {
17
+ this.minLevel = this._getMinLevel();
18
+ }
19
+
20
+ _getMinLevel() {
21
+ const level = process.env.FRIGG_LOG_LEVEL || 'INFO';
22
+ return LOG_LEVELS[level.toUpperCase()] ?? LOG_LEVELS.INFO;
23
+ }
24
+
25
+ _shouldLog(level) {
26
+ return LOG_LEVELS[level] >= this.minLevel;
27
+ }
28
+
29
+ _sanitize(message) {
30
+ // Remove potential key material or encrypted data from logs
31
+ if (typeof message === 'string') {
32
+ // Truncate long base64 strings that might be keys or encrypted data
33
+ return message.replace(/([A-Za-z0-9+/=]{50,})/g, (match) =>
34
+ `${match.substring(0, 10)}...[${match.length} chars]`
35
+ );
36
+ }
37
+ return message;
38
+ }
39
+
40
+ debug(message, ...args) {
41
+ if (this._shouldLog('DEBUG')) {
42
+ console.log(`[Frigg Debug]`, this._sanitize(message), ...args);
43
+ }
44
+ }
45
+
46
+ info(message, ...args) {
47
+ if (this._shouldLog('INFO')) {
48
+ console.log(`[Frigg]`, this._sanitize(message), ...args);
49
+ }
50
+ }
51
+
52
+ warn(message, ...args) {
53
+ if (this._shouldLog('WARN')) {
54
+ console.warn(`[Frigg]`, this._sanitize(message), ...args);
55
+ }
56
+ }
57
+
58
+ error(message, error) {
59
+ if (this._shouldLog('ERROR')) {
60
+ const sanitizedMessage = this._sanitize(message);
61
+
62
+ // In production, don't log stack traces with sensitive paths
63
+ const isProduction = process.env.STAGE === 'production';
64
+
65
+ if (error && !isProduction) {
66
+ console.error(`[Frigg]`, sanitizedMessage, error);
67
+ } else if (error) {
68
+ console.error(`[Frigg]`, sanitizedMessage, error.message);
69
+ } else {
70
+ console.error(`[Frigg]`, sanitizedMessage);
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ // Singleton instance
77
+ const logger = new EncryptionLogger();
78
+
79
+ module.exports = { logger };
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Prisma Client Extension for transparent field-level encryption.
3
+ * Intercepts Prisma queries to encrypt on write and decrypt on read.
4
+ */
5
+
6
+ const {
7
+ getEncryptedFields,
8
+ getFieldsToEncryptOnWrite,
9
+ getFieldsToDecryptOnRead,
10
+ } = require('./encryption-schema-registry');
11
+ const { FieldEncryptionService } = require('./field-encryption-service');
12
+
13
+ function createEncryptionExtension({ cryptor, enabled = true }) {
14
+ if (!enabled) {
15
+ return (client) => client;
16
+ }
17
+
18
+ if (!cryptor) {
19
+ throw new Error(
20
+ 'Cryptor instance required for encryption extension'
21
+ );
22
+ }
23
+
24
+ const encryptionService = new FieldEncryptionService({
25
+ cryptor,
26
+ schema: {
27
+ getEncryptedFields,
28
+ getFieldsToEncryptOnWrite,
29
+ getFieldsToDecryptOnRead,
30
+ },
31
+ });
32
+
33
+ return {
34
+ name: 'frigg-field-encryption',
35
+ query: {
36
+ $allModels: {
37
+ async create({ model, args, query }) {
38
+ if (args.data) {
39
+ args.data = await encryptionService.encryptFields(
40
+ model,
41
+ args.data
42
+ );
43
+ }
44
+
45
+ const result = await query(args);
46
+
47
+ if (result) {
48
+ return await encryptionService.decryptFields(
49
+ model,
50
+ result
51
+ );
52
+ }
53
+
54
+ return result;
55
+ },
56
+
57
+ async createMany({ model, args, query }) {
58
+ if (args.data && Array.isArray(args.data)) {
59
+ args.data =
60
+ await encryptionService.encryptFieldsInBulk(
61
+ model,
62
+ args.data
63
+ );
64
+ } else if (args.data) {
65
+ args.data = await encryptionService.encryptFields(
66
+ model,
67
+ args.data
68
+ );
69
+ }
70
+
71
+ return await query(args);
72
+ },
73
+
74
+ async update({ model, args, query }) {
75
+ if (args.data) {
76
+ args.data = await encryptionService.encryptFields(
77
+ model,
78
+ args.data
79
+ );
80
+ }
81
+
82
+ const result = await query(args);
83
+
84
+ if (result) {
85
+ return await encryptionService.decryptFields(
86
+ model,
87
+ result
88
+ );
89
+ }
90
+
91
+ return result;
92
+ },
93
+
94
+ async updateMany({ model, args, query }) {
95
+ if (args.data) {
96
+ args.data = await encryptionService.encryptFields(
97
+ model,
98
+ args.data
99
+ );
100
+ }
101
+
102
+ return await query(args);
103
+ },
104
+
105
+ async upsert({ model, args, query }) {
106
+ if (args.create) {
107
+ args.create = await encryptionService.encryptFields(
108
+ model,
109
+ args.create
110
+ );
111
+ }
112
+
113
+ if (args.update) {
114
+ args.update = await encryptionService.encryptFields(
115
+ model,
116
+ args.update
117
+ );
118
+ }
119
+
120
+ const result = await query(args);
121
+
122
+ if (result) {
123
+ return await encryptionService.decryptFields(
124
+ model,
125
+ result
126
+ );
127
+ }
128
+
129
+ return result;
130
+ },
131
+
132
+ async findUnique({ model, args, query }) {
133
+ const result = await query(args);
134
+
135
+ if (result) {
136
+ return await encryptionService.decryptFields(
137
+ model,
138
+ result
139
+ );
140
+ }
141
+
142
+ return result;
143
+ },
144
+
145
+ async findFirst({ model, args, query }) {
146
+ const result = await query(args);
147
+
148
+ if (result) {
149
+ return await encryptionService.decryptFields(
150
+ model,
151
+ result
152
+ );
153
+ }
154
+
155
+ return result;
156
+ },
157
+
158
+ async findMany({ model, args, query }) {
159
+ const results = await query(args);
160
+
161
+ if (results && Array.isArray(results)) {
162
+ return await encryptionService.decryptFieldsInBulk(
163
+ model,
164
+ results
165
+ );
166
+ }
167
+
168
+ return results;
169
+ },
170
+
171
+ async delete({ model, args, query }) {
172
+ const result = await query(args);
173
+
174
+ if (result) {
175
+ return await encryptionService.decryptFields(
176
+ model,
177
+ result
178
+ );
179
+ }
180
+
181
+ return result;
182
+ },
183
+
184
+ async deleteMany({ model, args, query }) {
185
+ return await query(args);
186
+ },
187
+
188
+ async count({ model, args, query }) {
189
+ return await query(args);
190
+ },
191
+
192
+ async aggregate({ model, args, query }) {
193
+ return await query(args);
194
+ },
195
+
196
+ async groupBy({ model, args, query }) {
197
+ return await query(args);
198
+ },
199
+
200
+ async findFirstOrThrow({ model, args, query }) {
201
+ const result = await query(args);
202
+
203
+ if (result) {
204
+ return await encryptionService.decryptFields(
205
+ model,
206
+ result
207
+ );
208
+ }
209
+
210
+ return result;
211
+ },
212
+
213
+ async findUniqueOrThrow({ model, args, query }) {
214
+ const result = await query(args);
215
+
216
+ if (result) {
217
+ return await encryptionService.decryptFields(
218
+ model,
219
+ result
220
+ );
221
+ }
222
+
223
+ return result;
224
+ },
225
+ },
226
+ },
227
+ };
228
+ }
229
+
230
+ module.exports = { createEncryptionExtension };
package/database/index.js CHANGED
@@ -1,25 +1,25 @@
1
- const { mongoose } = require('./mongoose');
1
+ /**
2
+ * Database Module Index
3
+ * Exports Prisma client, connection utilities, and repositories
4
+ *
5
+ * Note: Frigg uses the Repository pattern for data access.
6
+ * Use repositories for data operations:
7
+ * - SyncRepository (syncs/sync-repository.js)
8
+ * - IntegrationRepository (integrations/integration-repository.js)
9
+ * - CredentialRepository (credential/credential-repository.js)
10
+ * etc.
11
+ */
12
+
13
+ const { prisma, connectPrisma, disconnectPrisma } = require('./prisma');
14
+ const { TokenRepository } = require('../token/repositories/token-repository');
2
15
  const {
3
- connectToDatabase,
4
- disconnectFromDatabase,
5
- createObjectId,
6
- } = require('./mongo');
7
- const { IndividualUser } = require('./models/IndividualUser');
8
- const { OrganizationUser } = require('./models/OrganizationUser');
9
- const { State } = require('./models/State');
10
- const { Token } = require('./models/Token');
11
- const { UserModel } = require('./models/UserModel');
12
- const { WebsocketConnection } = require('./models/WebsocketConnection');
16
+ WebsocketConnectionRepository,
17
+ } = require('../websocket/repositories/websocket-connection-repository');
13
18
 
14
19
  module.exports = {
15
- mongoose,
16
- connectToDatabase,
17
- disconnectFromDatabase,
18
- createObjectId,
19
- IndividualUser,
20
- OrganizationUser,
21
- State,
22
- Token,
23
- UserModel,
24
- WebsocketConnection,
20
+ prisma,
21
+ connectPrisma,
22
+ disconnectPrisma,
23
+ TokenRepository,
24
+ WebsocketConnectionRepository,
25
25
  };
@@ -0,0 +1,182 @@
1
+ const {
2
+ createEncryptionExtension,
3
+ } = require('./encryption/prisma-encryption-extension');
4
+ const { loadCustomEncryptionSchema } = require('./encryption/encryption-schema-registry');
5
+ const { logger } = require('./encryption/logger');
6
+ const { Cryptor } = require('../encrypt/Cryptor');
7
+ const config = require('./config');
8
+
9
+ /**
10
+ * Ensures DATABASE_URL is set for MongoDB connections
11
+ * Falls back to MONGO_URI if DATABASE_URL is not set
12
+ * Infrastructure layer concern - maps legacy MONGO_URI to Prisma's expected DATABASE_URL
13
+ *
14
+ * Note: This should only be called when DB_TYPE is 'mongodb' or 'documentdb'
15
+ */
16
+ function ensureMongoDbUrl() {
17
+ // If DATABASE_URL is already set, use it
18
+ if (process.env.DATABASE_URL && process.env.DATABASE_URL.trim()) {
19
+ return;
20
+ }
21
+
22
+ // Fallback to MONGO_URI for backwards compatibility with DocumentDB deployments
23
+ if (process.env.MONGO_URI && process.env.MONGO_URI.trim()) {
24
+ process.env.DATABASE_URL = process.env.MONGO_URI;
25
+ logger.debug('Using MONGO_URI as DATABASE_URL for Mongo-compatible connection');
26
+ return;
27
+ }
28
+
29
+ // Neither is set - error
30
+ throw new Error(
31
+ 'DATABASE_URL or MONGO_URI environment variable must be set for MongoDB/DocumentDB'
32
+ );
33
+ }
34
+
35
+ function getEncryptionConfig() {
36
+ const STAGE = process.env.STAGE || process.env.NODE_ENV || 'development';
37
+ const shouldBypassEncryption = ['dev', 'test', 'local'].includes(STAGE);
38
+
39
+ if (shouldBypassEncryption) {
40
+ return { enabled: false };
41
+ }
42
+
43
+ const hasKMS =
44
+ process.env.KMS_KEY_ARN && process.env.KMS_KEY_ARN.trim() !== '';
45
+ const hasAES =
46
+ process.env.AES_KEY_ID && process.env.AES_KEY_ID.trim() !== '';
47
+
48
+ if (!hasKMS && !hasAES) {
49
+ logger.warn(
50
+ 'No encryption keys configured (KMS_KEY_ARN or AES_KEY_ID). ' +
51
+ 'Field-level encryption disabled. Set STAGE=production and configure keys to enable.'
52
+ );
53
+ return { enabled: false };
54
+ }
55
+
56
+ return {
57
+ enabled: true,
58
+ method: hasKMS ? 'kms' : 'aes',
59
+ };
60
+ }
61
+
62
+ const prismaClientSingleton = () => {
63
+ let PrismaClient;
64
+
65
+ // Helper to try loading Prisma client from multiple locations
66
+ const loadPrismaClient = (dbType) => {
67
+ const paths = [
68
+ // Lambda layer location (when using Prisma Lambda layer)
69
+ `/opt/nodejs/node_modules/generated/prisma-${dbType}`,
70
+ // Local development location (relative to core package)
71
+ `../generated/prisma-${dbType}`,
72
+ ];
73
+
74
+ for (const path of paths) {
75
+ try {
76
+ return require(path).PrismaClient;
77
+ } catch (err) {
78
+ // Continue to next path
79
+ }
80
+ }
81
+
82
+ throw new Error(
83
+ `Cannot find Prisma client for ${dbType}. Tried paths: ${paths.join(', ')}`
84
+ );
85
+ };
86
+
87
+ if (config.DB_TYPE === 'mongodb' || config.DB_TYPE === 'documentdb') {
88
+ // Ensure DATABASE_URL is set (fallback to MONGO_URI if needed)
89
+ ensureMongoDbUrl();
90
+ PrismaClient = loadPrismaClient('mongodb');
91
+ } else if (config.DB_TYPE === 'postgresql') {
92
+ PrismaClient = loadPrismaClient('postgresql');
93
+ } else {
94
+ throw new Error(
95
+ `Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
96
+ );
97
+ }
98
+
99
+ let client = new PrismaClient({
100
+ log: process.env.PRISMA_LOG_LEVEL
101
+ ? process.env.PRISMA_LOG_LEVEL.split(',')
102
+ : ['error', 'warn'],
103
+ errorFormat: 'pretty',
104
+ });
105
+
106
+ const encryptionConfig = getEncryptionConfig();
107
+
108
+ if (encryptionConfig.enabled) {
109
+ try {
110
+ // Load custom encryption schema from appDefinition before creating extension
111
+ loadCustomEncryptionSchema();
112
+
113
+ const cryptor = new Cryptor({
114
+ shouldUseAws: encryptionConfig.method === 'kms',
115
+ });
116
+
117
+ client = client.$extends(
118
+ createEncryptionExtension({
119
+ cryptor,
120
+ enabled: true,
121
+ })
122
+ );
123
+
124
+ logger.info(
125
+ `Field-level encryption enabled using ${encryptionConfig.method.toUpperCase()}`
126
+ );
127
+ } catch (error) {
128
+ logger.error(
129
+ 'Failed to initialize encryption extension:',
130
+ error
131
+ );
132
+ logger.warn('Continuing without encryption...');
133
+ }
134
+ } else {
135
+ logger.info('Field-level encryption disabled');
136
+ }
137
+
138
+ return client;
139
+ };
140
+
141
+ const globalForPrisma = global;
142
+
143
+ // Lazy initialization - only create singleton when first accessed
144
+ function getPrismaClient() {
145
+ if (!globalForPrisma._prismaInstance) {
146
+ globalForPrisma._prismaInstance = prismaClientSingleton();
147
+ }
148
+ return globalForPrisma._prismaInstance;
149
+ }
150
+
151
+ // Export a getter for lazy initialization
152
+ const prisma = new Proxy({}, {
153
+ get(target, prop) {
154
+ return getPrismaClient()[prop];
155
+ }
156
+ });
157
+
158
+ async function disconnectPrisma() {
159
+ await getPrismaClient().$disconnect();
160
+ }
161
+
162
+ async function connectPrisma() {
163
+ await getPrismaClient().$connect();
164
+
165
+ // Initialize MongoDB schema - ensure all collections exist
166
+ // Only run for MongoDB/DocumentDB (not PostgreSQL)
167
+ // This prevents "Cannot create namespace in multi-document transaction" errors
168
+ if (config.DB_TYPE === 'mongodb' || config.DB_TYPE === 'documentdb') {
169
+ const { initializeMongoDBSchema } = require('./utils/mongodb-schema-init');
170
+ await initializeMongoDBSchema();
171
+ }
172
+
173
+ return getPrismaClient();
174
+ }
175
+
176
+ module.exports = {
177
+ prisma,
178
+ connectPrisma,
179
+ disconnectPrisma,
180
+ getEncryptionConfig,
181
+ ensureMongoDbUrl, // Exported for testing
182
+ };
@@ -0,0 +1,138 @@
1
+ const {
2
+ HealthCheckRepositoryInterface,
3
+ } = require('./health-check-repository-interface');
4
+ const {
5
+ toObjectId,
6
+ fromObjectId,
7
+ findOne,
8
+ insertOne,
9
+ deleteOne,
10
+ } = require('../documentdb-utils');
11
+ const { DocumentDBEncryptionService } = require('../documentdb-encryption-service');
12
+
13
+ class HealthCheckRepositoryDocumentDB extends HealthCheckRepositoryInterface {
14
+ /**
15
+ * @param {Object} params
16
+ * @param {Object} params.prismaClient - Prisma client instance
17
+ */
18
+ constructor({ prismaClient }) {
19
+ super();
20
+ this.prisma = prismaClient;
21
+ this.encryptionService = new DocumentDBEncryptionService();
22
+ }
23
+
24
+ /**
25
+ * @returns {Promise<{readyState: number, stateName: string, isConnected: boolean}>}
26
+ */
27
+ async getDatabaseConnectionState() {
28
+ let isConnected = false;
29
+ let stateName = 'unknown';
30
+
31
+ try {
32
+ await this.prisma.$runCommandRaw({ ping: 1 });
33
+ isConnected = true;
34
+ stateName = 'connected';
35
+ } catch (error) {
36
+ stateName = 'disconnected';
37
+ }
38
+
39
+ return {
40
+ readyState: isConnected ? 1 : 0,
41
+ stateName,
42
+ isConnected,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * @param {number} maxTimeMS
48
+ * @returns {Promise<number>} Response time in milliseconds
49
+ */
50
+ async pingDatabase(maxTimeMS = 2000) {
51
+ const pingStart = Date.now();
52
+ let timeoutId;
53
+
54
+ const timeoutPromise = new Promise((_, reject) => {
55
+ timeoutId = setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS);
56
+ });
57
+
58
+ try {
59
+ await Promise.race([
60
+ this.prisma.$runCommandRaw({ ping: 1 }),
61
+ timeoutPromise,
62
+ ]);
63
+ return Date.now() - pingStart;
64
+ } finally {
65
+ clearTimeout(timeoutId);
66
+ }
67
+ }
68
+
69
+ async createCredential(credentialData) {
70
+ const now = new Date();
71
+ const document = {
72
+ ...credentialData,
73
+ createdAt: now,
74
+ updatedAt: now,
75
+ };
76
+
77
+ // Encrypt sensitive fields before insert
78
+ const encryptedDocument = await this.encryptionService.encryptFields(
79
+ 'Credential',
80
+ document
81
+ );
82
+ const insertedId = await insertOne(this.prisma, 'Credential', encryptedDocument);
83
+ const created = await findOne(this.prisma, 'Credential', { _id: insertedId });
84
+
85
+ // Decrypt after read
86
+ const decrypted = await this.encryptionService.decryptFields(
87
+ 'Credential',
88
+ created
89
+ );
90
+
91
+ return {
92
+ id: fromObjectId(decrypted._id),
93
+ ...decrypted,
94
+ };
95
+ }
96
+
97
+ async findCredentialById(id) {
98
+ const doc = await findOne(this.prisma, 'Credential', {
99
+ _id: toObjectId(id),
100
+ });
101
+
102
+ if (!doc) return null;
103
+
104
+ // Decrypt sensitive fields
105
+ const decrypted = await this.encryptionService.decryptFields('Credential', doc);
106
+
107
+ return {
108
+ id: fromObjectId(decrypted._id),
109
+ ...decrypted,
110
+ };
111
+ }
112
+
113
+ async getRawCredentialById(id) {
114
+ const objectId = toObjectId(id);
115
+ if (!objectId) return null;
116
+
117
+ const result = await this.prisma.$runCommandRaw({
118
+ find: 'Credential',
119
+ filter: { _id: objectId },
120
+ });
121
+
122
+ // Return raw document WITHOUT decryption
123
+ // This allows the test to verify that fields are actually encrypted in the database
124
+ return result?.cursor?.firstBatch?.[0] ?? null;
125
+ }
126
+
127
+ async deleteCredential(id) {
128
+ const objectId = toObjectId(id);
129
+ if (!objectId) return false;
130
+
131
+ const result = await deleteOne(this.prisma, 'Credential', { _id: objectId });
132
+ const deleted = result?.n ?? 0;
133
+ return deleted > 0;
134
+ }
135
+ }
136
+
137
+ module.exports = { HealthCheckRepositoryDocumentDB };
138
+