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

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,274 @@
1
+ const { Delegate } = require('../core');
2
+ const _ = require('lodash');
3
+ const { flushDebugLog } = require('../logs');
4
+ const { ModuleConstants } = require('./ModuleConstants');
5
+ const {
6
+ createCredentialRepository,
7
+ } = require('../credential/repositories/credential-repository-factory');
8
+ const {
9
+ createModuleRepository,
10
+ } = require('./repositories/module-repository-factory');
11
+
12
+ // todo: this class should be a Domain class, and the Delegate function is preventing us from
13
+ // doing that, we probably have to get rid of the Delegate class as well as the event based
14
+ // calls since they go against the Domain Driven Design principles (eg. a domain class should not call repository methods or use cases)
15
+ class Module extends Delegate {
16
+ //todo: entity should be replaced with actual entity properties
17
+ /**
18
+ *
19
+ * @param {Object} params
20
+ * @param {Object} params.definition The definition of the Api Module
21
+ * @param {string} params.userId The user id
22
+ * @param {Object} params.entity The entity record from the database
23
+ * @param {string} [params.state] Optional OAuth state value forwarded to the API client (round-trips through the OAuth provider).
24
+ */
25
+ constructor({ definition, userId = null, entity: entityObj = null, state = null }) {
26
+ super({ definition, userId, entity: entityObj });
27
+
28
+ this.validateDefinition(definition);
29
+
30
+ this.userId = userId;
31
+ this.entity = entityObj;
32
+ this.credential = entityObj?.credential;
33
+ this.definition = definition;
34
+ this.name = this.definition.moduleName;
35
+ this.modelName = this.definition.modelName;
36
+ this.apiClass = this.definition.API;
37
+
38
+ this.credentialRepository = createCredentialRepository();
39
+ this.moduleRepository = createModuleRepository();
40
+
41
+ // Module → parent delegate (typically IntegrationBase) events
42
+ this.DLGT_CREDENTIAL_INVALIDATED = 'CREDENTIAL_INVALIDATED';
43
+ this.delegateTypes.push(this.DLGT_CREDENTIAL_INVALIDATED);
44
+ this.DLGT_CREDENTIAL_VALIDATED = 'CREDENTIAL_VALIDATED';
45
+ this.delegateTypes.push(this.DLGT_CREDENTIAL_VALIDATED);
46
+
47
+ Object.assign(this, this.definition.requiredAuthMethods);
48
+
49
+ const apiParams = {
50
+ ...this.definition.env,
51
+ delegate: this,
52
+ ...(state ? { state } : {}),
53
+ ...(this.credential?.data
54
+ ? this.apiParamsFromCredential(this.credential.data)
55
+ : {}), // Handle case when credential is undefined
56
+ ...this.apiParamsFromEntity(this.entity),
57
+ };
58
+ this.api = new this.apiClass(apiParams);
59
+ }
60
+
61
+ getName() {
62
+ return this.name;
63
+ }
64
+
65
+ getEntityOptions() {
66
+ return this.definition.getEntityOptions();
67
+ }
68
+
69
+ async refreshEntityOptions(options) {
70
+ await this.definition.refreshEntityOptions(options);
71
+ return this.getEntityOptions();
72
+ }
73
+
74
+ apiParamsFromCredential(credential) {
75
+ return _.pick(credential, ...this.apiPropertiesToPersist?.credential);
76
+ }
77
+
78
+ apiParamsFromEntity(entity) {
79
+ return _.pick(entity, ...this.apiPropertiesToPersist?.entity);
80
+ }
81
+
82
+ validateAuthorizationRequirements() {
83
+ const requirements = this.getAuthorizationRequirements();
84
+ let valid = true;
85
+ if (
86
+ ['oauth1', 'oauth2'].includes(requirements.type) &&
87
+ !requirements.url
88
+ ) {
89
+ valid = false;
90
+ }
91
+ return valid;
92
+ }
93
+
94
+ getAuthorizationRequirements(params) {
95
+ return this.api.getAuthorizationRequirements();
96
+ }
97
+
98
+ async testAuth() {
99
+ let validAuth = false;
100
+ try {
101
+ if (await this.testAuthRequest(this.api)) validAuth = true;
102
+ } catch (e) {
103
+ flushDebugLog(e);
104
+ }
105
+ return validAuth;
106
+ }
107
+
108
+ async onTokenUpdate() {
109
+ const credentialDetails = await this.getCredentialDetails(
110
+ this.api,
111
+ this.userId
112
+ );
113
+ const apiParams = this.apiParamsFromCredential(this.api);
114
+
115
+ if (!apiParams.refresh_token && this.api.isRefreshable) {
116
+ console.warn(
117
+ `[Frigg] No refresh_token in apiParams for module ${this.name}.`
118
+ );
119
+ }
120
+
121
+ Object.assign(credentialDetails.details, apiParams);
122
+ credentialDetails.details.authIsValid = true;
123
+
124
+ const persisted = await this.credentialRepository.upsertCredential(
125
+ credentialDetails
126
+ );
127
+ this.credential = persisted;
128
+
129
+ if (this.credential?.id) {
130
+ try {
131
+ await this.notify(this.DLGT_CREDENTIAL_VALIDATED, {
132
+ credentialId: this.credential.id,
133
+ moduleName: this.name,
134
+ });
135
+ } catch (err) {
136
+ console.error(
137
+ `[Frigg] Failed to propagate CREDENTIAL_VALIDATED for module ${this.name}:`,
138
+ err?.message || err
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ async receiveNotification(notifier, delegateString, object = null) {
145
+ if (delegateString === this.api.DLGT_TOKEN_UPDATE) {
146
+ await this.onTokenUpdate();
147
+ } else if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
148
+ await this.deauthorize();
149
+ } else if (delegateString === this.api.DLGT_INVALID_AUTH) {
150
+ await this.markCredentialsInvalid();
151
+ }
152
+ }
153
+
154
+ async markCredentialsInvalid() {
155
+ if (!this.credential) return;
156
+
157
+ if (!this.credential.id) return;
158
+
159
+ await this.credentialRepository.updateAuthenticationStatus(
160
+ this.credential.id,
161
+ false
162
+ );
163
+
164
+ // Keep the in-memory snapshot consistent so that callers can read the
165
+ // updated state without another fetch.
166
+ this.credential.authIsValid = false;
167
+
168
+ // Propagate upward so a parent delegate (e.g. IntegrationBase) can
169
+ // react — for instance by flipping Integration.status to DISABLED.
170
+ // Delegate.notify is a silent no-op when this.delegate is null, so
171
+ // Module instances constructed outside of an Integration context
172
+ // (e.g. during ProcessAuthorizationCallback) remain unaffected.
173
+ //
174
+ // Best-effort: this method is invoked from the OAuth2Requester 401
175
+ // refresh catch block, which depends on us NOT throwing. A DB hiccup
176
+ // in the downstream status flip must not alter refreshAuth's
177
+ // documented `return false` contract. The credential has already
178
+ // been persisted as invalid; integrations left un-flipped can be
179
+ // recovered by the next retry or by operator intervention.
180
+ try {
181
+ await this.notify(this.DLGT_CREDENTIAL_INVALIDATED, {
182
+ credentialId: this.credential.id,
183
+ moduleName: this.name,
184
+ });
185
+ } catch (err) {
186
+ console.error(
187
+ `[Frigg] Failed to propagate CREDENTIAL_INVALIDATED for module ${this.name}:`,
188
+ err?.message || err
189
+ );
190
+ }
191
+ }
192
+
193
+ async deauthorize() {
194
+ //todo: Check if this is correct, we're instantiating a new api without params (credentials, tokens, etc...)
195
+ this.api = new this.apiClass();
196
+
197
+ // Remove persisted credential (if any)
198
+ if (this.entity?.credential) {
199
+ const credentialId =
200
+ this.entity.credential.id || this.entity.credential;
201
+
202
+ // Delete credential via repository
203
+ await this.credentialRepository.deleteCredentialById(credentialId);
204
+
205
+ // Unset credential reference on the Entity document
206
+ const entityId = this.entity.id;
207
+ if (entityId) {
208
+ await this.moduleRepository.unsetCredential(entityId);
209
+ }
210
+
211
+ // Keep in-memory snapshot consistent
212
+ this.entity.credential = undefined;
213
+ }
214
+ }
215
+
216
+ // todo: check if all these props are still up to date
217
+ validateDefinition(definition) {
218
+ if (!definition) {
219
+ throw new Error('Module definition is required');
220
+ }
221
+ if (!definition.moduleName) {
222
+ throw new Error('Module definition requires moduleName');
223
+ }
224
+ if (!definition.API) {
225
+ throw new Error('Module definition requires API class');
226
+ }
227
+ if (!definition.requiredAuthMethods) {
228
+ throw new Error('Module definition requires requiredAuthMethods');
229
+ } else {
230
+ if (
231
+ definition.API.requesterType ===
232
+ ModuleConstants.authType.oauth2 &&
233
+ !definition.requiredAuthMethods.getToken
234
+ ) {
235
+ throw new Error(
236
+ 'Module definition requires requiredAuthMethods.getToken'
237
+ );
238
+ }
239
+ if (!definition.requiredAuthMethods.getEntityDetails) {
240
+ throw new Error(
241
+ 'Module definition requires requiredAuthMethods.getEntityDetails'
242
+ );
243
+ }
244
+ if (!definition.requiredAuthMethods.getCredentialDetails) {
245
+ throw new Error(
246
+ 'Module definition requires requiredAuthMethods.getCredentialDetails'
247
+ );
248
+ }
249
+ if (!definition.requiredAuthMethods.apiPropertiesToPersist) {
250
+ throw new Error(
251
+ 'Module definition requires requiredAuthMethods.apiPropertiesToPersist'
252
+ );
253
+ } else if (definition.Credential) {
254
+ for (const prop of definition.requiredAuthMethods
255
+ .apiPropertiesToPersist?.credential) {
256
+ if (
257
+ !definition.Credential.schema.paths.hasOwnProperty(prop)
258
+ ) {
259
+ throw new Error(
260
+ `Module definition requires Credential schema to have property ${prop}`
261
+ );
262
+ }
263
+ }
264
+ }
265
+ if (!definition.requiredAuthMethods.testAuthRequest) {
266
+ throw new Error(
267
+ 'Module definition requires requiredAuthMethods.testAuth'
268
+ );
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ module.exports = { Module };
@@ -0,0 +1,350 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ toObjectId,
4
+ fromObjectId,
5
+ findMany,
6
+ findOne,
7
+ insertOne,
8
+ updateOne,
9
+ deleteOne,
10
+ } = require('../../database/documentdb-utils');
11
+ const { ModuleRepositoryInterface } = require('./module-repository-interface');
12
+ const { DocumentDBEncryptionService } = require('../../database/documentdb-encryption-service');
13
+
14
+ /**
15
+ * Module/Entity repository for DocumentDB.
16
+ * Uses DocumentDBEncryptionService for credential decryption.
17
+ *
18
+ * Encrypted fields: Credential.data.*
19
+ *
20
+ * Note: This repository only reads credentials. CredentialRepository
21
+ * handles credential creation/updates with encryption.
22
+ *
23
+ * @see DocumentDBEncryptionService
24
+ * @see CredentialRepositoryDocumentDB
25
+ */
26
+ class ModuleRepositoryDocumentDB extends ModuleRepositoryInterface {
27
+ constructor() {
28
+ super();
29
+ this.prisma = prisma;
30
+ this.encryptionService = new DocumentDBEncryptionService();
31
+ }
32
+
33
+ async findEntityById(entityId) {
34
+ const objectId = toObjectId(entityId);
35
+ if (!objectId) {
36
+ throw new Error(`Entity ${entityId} not found`);
37
+ }
38
+ const doc = await findOne(this.prisma, 'Entity', { _id: objectId });
39
+ if (!doc) {
40
+ throw new Error(`Entity ${entityId} not found`);
41
+ }
42
+ const credential = await this._fetchCredential(doc.credentialId);
43
+ return this._mapEntity(doc, credential);
44
+ }
45
+
46
+ async findEntitiesByUserId(userId) {
47
+ const objectId = toObjectId(userId);
48
+ if (!objectId) {
49
+ throw new Error(`Invalid userId: ${userId}`);
50
+ }
51
+ const filter = { userId: objectId };
52
+ const docs = await findMany(this.prisma, 'Entity', filter);
53
+ const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
54
+ return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
55
+ }
56
+
57
+ async findEntitiesByIds(entitiesIds) {
58
+ const ids = (entitiesIds || []).map((id) => toObjectId(id)).filter(Boolean);
59
+ if (ids.length === 0) return [];
60
+ const docs = await findMany(this.prisma, 'Entity', { _id: { $in: ids } });
61
+ const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
62
+ return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
63
+ }
64
+
65
+ async findEntitiesByUserIdAndModuleName(userId, moduleName) {
66
+ const objectId = toObjectId(userId);
67
+ if (!objectId) {
68
+ throw new Error(`Invalid userId: ${userId}`);
69
+ }
70
+ const filter = {
71
+ userId: objectId,
72
+ moduleName,
73
+ };
74
+ const docs = await findMany(this.prisma, 'Entity', filter);
75
+ const credentialMap = await this._fetchCredentialsBulk(docs.map((doc) => doc.credentialId));
76
+ return docs.map((doc) => this._mapEntity(doc, credentialMap.get(fromObjectId(doc.credentialId)) || null));
77
+ }
78
+
79
+ async unsetCredential(entityId) {
80
+ const objectId = toObjectId(entityId);
81
+ if (!objectId) return false;
82
+ await updateOne(
83
+ this.prisma,
84
+ 'Entity',
85
+ { _id: objectId },
86
+ {
87
+ $set: {
88
+ credentialId: null,
89
+ },
90
+ }
91
+ );
92
+ return true;
93
+ }
94
+
95
+ async findEntity(filter) {
96
+ const query = this._buildFilter(filter);
97
+ const doc = await findOne(this.prisma, 'Entity', query);
98
+ if (!doc) return null;
99
+ const credential = await this._fetchCredential(doc.credentialId);
100
+ return this._mapEntity(doc, credential);
101
+ }
102
+
103
+ async findEntities(filter) {
104
+ const query = this._buildFilter(filter);
105
+ const docs = await findMany(this.prisma, 'Entity', query);
106
+ if (!docs || docs.length === 0) return [];
107
+ const credentialMap = await this._fetchCredentialsBulk(
108
+ docs.map((doc) => doc.credentialId)
109
+ );
110
+ return docs.map((doc) =>
111
+ this._mapEntity(
112
+ doc,
113
+ credentialMap.get(fromObjectId(doc.credentialId)) || null
114
+ )
115
+ );
116
+ }
117
+
118
+ async createEntity(entityData) {
119
+ const {
120
+ user,
121
+ userId,
122
+ credential,
123
+ credentialId,
124
+ name,
125
+ moduleName,
126
+ externalId,
127
+ ...dynamicData
128
+ } = entityData;
129
+
130
+ const document = {
131
+ userId: toObjectId(userId || user),
132
+ credentialId: toObjectId(credentialId || credential) || null,
133
+ name: name ?? null,
134
+ moduleName: moduleName ?? null,
135
+ externalId: externalId ?? null,
136
+ data: dynamicData,
137
+ };
138
+ const insertedId = await insertOne(this.prisma, 'Entity', document);
139
+ const created = await findOne(this.prisma, 'Entity', { _id: insertedId });
140
+ const credentialObj = await this._fetchCredential(created?.credentialId);
141
+ return this._mapEntity(created, credentialObj);
142
+ }
143
+
144
+ async updateEntity(entityId, updates) {
145
+ const objectId = toObjectId(entityId);
146
+ if (!objectId) return null;
147
+
148
+ const existing = await findOne(this.prisma, 'Entity', { _id: objectId });
149
+ if (!existing) return null;
150
+
151
+ const {
152
+ user,
153
+ userId,
154
+ credential,
155
+ credentialId,
156
+ name,
157
+ moduleName,
158
+ externalId,
159
+ ...dynamicData
160
+ } = updates;
161
+
162
+ const updatePayload = {};
163
+ if (user !== undefined || userId !== undefined) {
164
+ updatePayload.userId = toObjectId(userId || user) || null;
165
+ }
166
+ if (credential !== undefined || credentialId !== undefined) {
167
+ updatePayload.credentialId = toObjectId(credentialId || credential) || null;
168
+ }
169
+ if (name !== undefined) updatePayload.name = name;
170
+ if (moduleName !== undefined) updatePayload.moduleName = moduleName;
171
+ if (externalId !== undefined) updatePayload.externalId = externalId;
172
+
173
+ if (Object.keys(dynamicData).length > 0) {
174
+ updatePayload.data = { ...(existing.data || {}), ...dynamicData };
175
+ }
176
+
177
+ await updateOne(
178
+ this.prisma,
179
+ 'Entity',
180
+ { _id: objectId },
181
+ { $set: updatePayload }
182
+ );
183
+ const updated = await findOne(this.prisma, 'Entity', { _id: objectId });
184
+ if (!updated) return null;
185
+ const credentialObj = await this._fetchCredential(updated?.credentialId);
186
+ return this._mapEntity(updated, credentialObj);
187
+ }
188
+
189
+ async deleteEntity(entityId) {
190
+ const objectId = toObjectId(entityId);
191
+ if (!objectId) return false;
192
+ const result = await deleteOne(this.prisma, 'Entity', { _id: objectId });
193
+ const deleted = result?.n ?? 0;
194
+ return deleted > 0;
195
+ }
196
+
197
+ async _fetchCredential(credentialId) {
198
+ const id = fromObjectId(credentialId);
199
+ if (!id) return null;
200
+
201
+ try {
202
+ // Convert to ObjectId for raw query
203
+ const objectId = toObjectId(id);
204
+ if (!objectId) return null;
205
+
206
+ // Use raw findOne to bypass Prisma encryption extension
207
+ const rawCredential = await findOne(this.prisma, 'Credential', {
208
+ _id: objectId
209
+ });
210
+
211
+ if (!rawCredential) return null;
212
+
213
+ // Decrypt sensitive fields using service
214
+ const decryptedCredential = await this.encryptionService.decryptFields('Credential', rawCredential);
215
+
216
+ // Return in same format
217
+ const credential = {
218
+ id: fromObjectId(decryptedCredential._id),
219
+ userId: fromObjectId(decryptedCredential.userId),
220
+ externalId: decryptedCredential.externalId ?? null,
221
+ authIsValid: decryptedCredential.authIsValid ?? null,
222
+ createdAt: decryptedCredential.createdAt,
223
+ updatedAt: decryptedCredential.updatedAt,
224
+ data: decryptedCredential.data
225
+ };
226
+
227
+ return this._convertCredentialIds(credential);
228
+ } catch (error) {
229
+ console.error(`Failed to fetch/decrypt credential ${id}:`, error.message);
230
+ // Return null instead of throwing to allow graceful degradation
231
+ // This repository is read-only (doesn't create/update credentials)
232
+ // Entities can still be loaded even if their credential is corrupted/unreadable
233
+ // The entity will have null credential, which calling code must handle
234
+ // This is intentional behavior: prefer partial data over complete failure
235
+ return null;
236
+ }
237
+ }
238
+
239
+ async _fetchCredentialsBulk(credentialIds) {
240
+ const ids = (credentialIds || [])
241
+ .map((value) => fromObjectId(value))
242
+ .filter((value) => value !== null && value !== undefined);
243
+ if (ids.length === 0) return new Map();
244
+
245
+ try {
246
+ // Convert string IDs to ObjectIds for bulk query
247
+ const objectIds = ids.map(id => toObjectId(id)).filter(Boolean);
248
+ if (objectIds.length === 0) return new Map();
249
+
250
+ // Use raw findMany to bypass Prisma encryption extension
251
+ const rawCredentials = await findMany(this.prisma, 'Credential', {
252
+ _id: { $in: objectIds }
253
+ });
254
+
255
+ // Decrypt all credentials in parallel
256
+ const decryptionPromises = rawCredentials.map(async (rawCredential) => {
257
+ try {
258
+ // Decrypt sensitive fields using service
259
+ const decryptedCredential = await this.encryptionService.decryptFields('Credential', rawCredential);
260
+
261
+ // Build credential object in same format as Prisma would return
262
+ const credential = {
263
+ id: fromObjectId(decryptedCredential._id),
264
+ userId: fromObjectId(decryptedCredential.userId),
265
+ externalId: decryptedCredential.externalId ?? null,
266
+ authIsValid: decryptedCredential.authIsValid ?? null,
267
+ createdAt: decryptedCredential.createdAt,
268
+ updatedAt: decryptedCredential.updatedAt,
269
+ data: decryptedCredential.data
270
+ };
271
+
272
+ return this._convertCredentialIds(credential);
273
+ } catch (error) {
274
+ const credId = fromObjectId(rawCredential._id);
275
+ console.error(`Failed to decrypt credential ${credId}:`, error.message);
276
+ return null;
277
+ }
278
+ });
279
+
280
+ // Wait for all decryptions to complete
281
+ const decryptedCredentials = await Promise.all(decryptionPromises);
282
+
283
+ // Build Map from results, filtering out nulls
284
+ const map = new Map();
285
+ decryptedCredentials.forEach(credential => {
286
+ if (credential) {
287
+ map.set(credential.id, credential);
288
+ }
289
+ });
290
+
291
+ return map;
292
+ } catch (error) {
293
+ console.error('Failed to fetch credentials bulk:', error.message);
294
+ return new Map();
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Convert credential object IDs to strings for application layer
300
+ * Ensures consistent credential format across database adapters
301
+ * @private
302
+ * @param {Object|null} credential - Credential object from database
303
+ * @returns {Object|null} Credential with properly formatted IDs
304
+ */
305
+ _convertCredentialIds(credential) {
306
+ if (!credential) return credential;
307
+ return {
308
+ ...credential,
309
+ id: credential.id ? String(credential.id) : null,
310
+ userId: credential.userId ? String(credential.userId) : null,
311
+ };
312
+ }
313
+
314
+ _buildFilter(filter) {
315
+ const query = {};
316
+ if (!filter) return query;
317
+ if (filter._id || filter.id) {
318
+ const idObj = toObjectId(filter._id || filter.id);
319
+ if (idObj) query._id = idObj;
320
+ }
321
+ if (filter.user || filter.userId) {
322
+ const userObj = toObjectId(filter.user || filter.userId);
323
+ if (userObj) query.userId = userObj;
324
+ }
325
+ if (filter.credential || filter.credentialId) {
326
+ const credObj = toObjectId(filter.credential || filter.credentialId);
327
+ if (credObj) query.credentialId = credObj;
328
+ }
329
+ if (filter.name) query.name = filter.name;
330
+ if (filter.moduleName) query.moduleName = filter.moduleName;
331
+ if (filter.externalId) query.externalId = filter.externalId;
332
+ return query;
333
+ }
334
+
335
+ _mapEntity(doc, credential) {
336
+ const dynamicData = doc?.data || {};
337
+ return {
338
+ id: fromObjectId(doc?._id),
339
+ credential,
340
+ userId: fromObjectId(doc?.userId),
341
+ name: doc?.name ?? null,
342
+ externalId: doc?.externalId ?? null,
343
+ moduleName: doc?.moduleName ?? null,
344
+ ...dynamicData,
345
+ };
346
+ }
347
+ }
348
+
349
+ module.exports = { ModuleRepositoryDocumentDB };
350
+
@@ -0,0 +1,40 @@
1
+ const { ModuleRepositoryMongo } = require('./module-repository-mongo');
2
+ const { ModuleRepositoryPostgres } = require('./module-repository-postgres');
3
+ const {
4
+ ModuleRepositoryDocumentDB,
5
+ } = require('./module-repository-documentdb');
6
+ const config = require('../../database/config');
7
+
8
+ /**
9
+ * Module Repository Factory
10
+ * Creates the appropriate repository adapter based on database type
11
+ *
12
+ * @returns {ModuleRepositoryInterface} Configured repository adapter
13
+ */
14
+ function createModuleRepository() {
15
+ const dbType = config.DB_TYPE;
16
+
17
+ switch (dbType) {
18
+ case 'mongodb':
19
+ return new ModuleRepositoryMongo();
20
+
21
+ case 'postgresql':
22
+ return new ModuleRepositoryPostgres();
23
+
24
+ case 'documentdb':
25
+ return new ModuleRepositoryDocumentDB();
26
+
27
+ default:
28
+ throw new Error(
29
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
30
+ );
31
+ }
32
+ }
33
+
34
+ module.exports = {
35
+ createModuleRepository,
36
+ // Export adapters for direct testing
37
+ ModuleRepositoryMongo,
38
+ ModuleRepositoryPostgres,
39
+ ModuleRepositoryDocumentDB,
40
+ };