@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * GetProcess Use Case
3
- *
3
+ *
4
4
  * Retrieves a process by ID with proper error handling.
5
5
  * Simple use case that delegates to repository.
6
- *
6
+ *
7
7
  * Design Philosophy:
8
8
  * - Use cases provide consistent error handling
9
9
  * - Business logic layer between controllers and repositories
10
10
  * - Return null for not found vs throwing error (configurable)
11
- *
11
+ *
12
12
  * @example
13
13
  * const getProcess = new GetProcess({ processRepository });
14
14
  * const process = await getProcess.execute(processId);
@@ -75,13 +75,12 @@ class GetProcess {
75
75
  }
76
76
 
77
77
  const processes = await Promise.all(
78
- processIds.map(id => this.execute(id))
78
+ processIds.map((id) => this.execute(id))
79
79
  );
80
80
 
81
81
  // Filter out nulls (not found)
82
- return processes.filter(p => p !== null);
82
+ return processes.filter((p) => p !== null);
83
83
  }
84
84
  }
85
85
 
86
86
  module.exports = { GetProcess };
87
-
@@ -6,6 +6,7 @@ const { CreateProcess } = require('./create-process');
6
6
  const { UpdateProcessState } = require('./update-process-state');
7
7
  const { UpdateProcessMetrics } = require('./update-process-metrics');
8
8
  const { GetProcess } = require('./get-process');
9
+ const { ExecuteProxyRequest } = require('./execute-proxy-request');
9
10
 
10
11
  module.exports = {
11
12
  GetIntegrationsForUser,
@@ -16,4 +17,5 @@ module.exports = {
16
17
  UpdateProcessState,
17
18
  UpdateProcessMetrics,
18
19
  GetProcess,
19
- };
20
+ ExecuteProxyRequest,
21
+ };
@@ -1,9 +1,5 @@
1
1
  class LoadIntegrationContextUseCase {
2
- constructor({
3
- integrationRepository,
4
- moduleRepository,
5
- moduleFactory,
6
- }) {
2
+ constructor({ integrationRepository, moduleRepository, moduleFactory }) {
7
3
  if (!integrationRepository) {
8
4
  throw new Error('integrationRepository is required');
9
5
  }
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * UpdateProcessState Use Case
3
- *
3
+ *
4
4
  * Updates the state of a process and optionally merges context updates.
5
5
  * Handles state transitions in the process state machine.
6
- *
6
+ *
7
7
  * Design Philosophy:
8
8
  * - State transitions are explicit and tracked
9
9
  * - Context updates are merged (not replaced) to preserve data
10
10
  * - Repository handles persistence, use case handles business logic
11
- *
11
+ *
12
12
  * State Machine (CRM Sync Example):
13
- * INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
13
+ * INITIALIZING → FETCHING_TOTAL → QUEUING_PAGES → PROCESSING_BATCHES →
14
14
  * COMPLETING → COMPLETED
15
- *
15
+ *
16
16
  * Any state can transition to ERROR on failure.
17
- *
17
+ *
18
18
  * @example
19
19
  * const updateProcessState = new UpdateProcessState({ processRepository });
20
20
  * await updateProcessState.execute(processId, 'FETCHING_TOTAL', {
@@ -75,7 +75,10 @@ class UpdateProcessState {
75
75
 
76
76
  // Persist updates
77
77
  try {
78
- const updatedProcess = await this.processRepository.update(processId, updates);
78
+ const updatedProcess = await this.processRepository.update(
79
+ processId,
80
+ updates
81
+ );
79
82
  return updatedProcess;
80
83
  } catch (error) {
81
84
  throw new Error(`Failed to update process state: ${error.message}`);
@@ -116,4 +119,3 @@ class UpdateProcessState {
116
119
  }
117
120
 
118
121
  module.exports = { UpdateProcessState };
119
-
@@ -15,11 +15,14 @@ function mapIntegrationClassToIntegrationDTO(integration) {
15
15
  version: integration.version,
16
16
  messages: integration.messages,
17
17
  userActions: integration.userActions,
18
- options: integration.options || (typeof integration.getOptionDetails === 'function' ? integration.getOptionDetails() : null),
18
+ options:
19
+ integration.options ||
20
+ (typeof integration.getOptionDetails === 'function'
21
+ ? integration.getOptionDetails()
22
+ : null),
19
23
  };
20
24
  }
21
25
 
22
-
23
26
  const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
24
27
  return [
25
28
  ...new Set(
@@ -34,4 +37,7 @@ const getModulesDefinitionFromIntegrationClasses = (integrationClasses) => {
34
37
  ];
35
38
  };
36
39
 
37
- module.exports = { mapIntegrationClassToIntegrationDTO, getModulesDefinitionFromIntegrationClasses };
40
+ module.exports = {
41
+ mapIntegrationClassToIntegrationDTO,
42
+ getModulesDefinitionFromIntegrationClasses,
43
+ };
package/jest-setup.js CHANGED
@@ -1,2 +1,2 @@
1
- const {globalSetup} = require('@friggframework/test');
1
+ const { globalSetup } = require('@friggframework/test');
2
2
  module.exports = globalSetup;
package/jest-teardown.js CHANGED
@@ -1,2 +1,2 @@
1
- const {globalTeardown} = require('@friggframework/test');
1
+ const { globalTeardown } = require('@friggframework/test');
2
2
  module.exports = globalTeardown;
@@ -2,42 +2,44 @@ const isPositive = (n) => Number.isFinite(n) && n > 0;
2
2
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
3
 
4
4
  class TimeoutCatcher {
5
- constructor({ work, timeout, cleanUp = () => {}, cleanUpTime = 2_000 }) {
6
- this.isFinished = false;
7
- this.work = work;
8
- this.cleanUp = cleanUp;
9
- this.waitTime = timeout - cleanUpTime;
10
-
11
- if (!isPositive(this.waitTime))
12
- throw new Error("Wait time was not a positive number of milliseconds");
13
- }
14
-
15
- async watch() {
16
- try {
17
- await Promise.race([this.doWork(), this.exitBeforeTimeout()]);
18
- return true;
19
- } catch (error) {
20
- if (error.isSentinelTimeout) return false;
21
- throw error;
5
+ constructor({ work, timeout, cleanUp = () => {}, cleanUpTime = 2_000 }) {
6
+ this.isFinished = false;
7
+ this.work = work;
8
+ this.cleanUp = cleanUp;
9
+ this.waitTime = timeout - cleanUpTime;
10
+
11
+ if (!isPositive(this.waitTime))
12
+ throw new Error(
13
+ 'Wait time was not a positive number of milliseconds'
14
+ );
22
15
  }
23
- }
24
16
 
25
- async doWork() {
26
- await this.work();
27
- this.isFinished = true;
28
- }
17
+ async watch() {
18
+ try {
19
+ await Promise.race([this.doWork(), this.exitBeforeTimeout()]);
20
+ return true;
21
+ } catch (error) {
22
+ if (error.isSentinelTimeout) return false;
23
+ throw error;
24
+ }
25
+ }
26
+
27
+ async doWork() {
28
+ await this.work();
29
+ this.isFinished = true;
30
+ }
29
31
 
30
- async exitBeforeTimeout() {
31
- await sleep(this.waitTime);
32
+ async exitBeforeTimeout() {
33
+ await sleep(this.waitTime);
32
34
 
33
- if (!this.isFinished) {
34
- await this.cleanUp();
35
+ if (!this.isFinished) {
36
+ await this.cleanUp();
35
37
 
36
- const error = new Error("Sentinel Timed Out");
37
- error.isSentinelTimeout = true;
38
- throw error;
38
+ const error = new Error('Sentinel Timed Out');
39
+ error.isSentinelTimeout = true;
40
+ throw error;
41
+ }
39
42
  }
40
- }
41
43
  }
42
44
 
43
45
  module.exports = { TimeoutCatcher };
package/lambda/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  const { TimeoutCatcher } = require('./TimeoutCatcher');
2
2
 
3
- module.exports = { TimeoutCatcher }
3
+ module.exports = { TimeoutCatcher };
package/logs/index.js CHANGED
@@ -1,7 +1,7 @@
1
- const {debug, initDebugLog, flushDebugLog} = require('./logger');
1
+ const { debug, initDebugLog, flushDebugLog } = require('./logger');
2
2
 
3
3
  module.exports = {
4
4
  debug,
5
5
  initDebugLog,
6
- flushDebugLog
7
- }
6
+ flushDebugLog,
7
+ };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * AuthorizationSession Entity
3
+ * Domain entity for multi-step authorization workflows
4
+ *
5
+ * Manages state for authentication flows that require multiple steps,
6
+ * such as OTP verification, multi-factor authentication, or progressive
7
+ * credential collection.
8
+ *
9
+ * @example
10
+ * const session = new AuthorizationSession({
11
+ * sessionId: '550e8400-e29b-41d4-a716-446655440000',
12
+ * userId: 'user123',
13
+ * entityType: 'nagaris',
14
+ * currentStep: 1,
15
+ * maxSteps: 2,
16
+ * stepData: { email: 'user@example.com' },
17
+ * expiresAt: new Date(Date.now() + 15 * 60 * 1000)
18
+ * });
19
+ */
20
+ class AuthorizationSession {
21
+ /**
22
+ * Create an authorization session
23
+ *
24
+ * @param {Object} params - Session parameters
25
+ * @param {string} params.sessionId - Unique session identifier (UUID)
26
+ * @param {string} params.userId - User ID who initiated the auth flow
27
+ * @param {string} params.entityType - Type of entity being authorized (module name)
28
+ * @param {number} [params.currentStep=1] - Current step in the auth flow
29
+ * @param {number} params.maxSteps - Total number of steps in the flow
30
+ * @param {Object} [params.stepData={}] - Accumulated data from previous steps
31
+ * @param {Date} params.expiresAt - Session expiration timestamp
32
+ * @param {boolean} [params.completed=false] - Whether auth flow is complete
33
+ * @param {Date} [params.createdAt=new Date()] - Session creation timestamp
34
+ * @param {Date} [params.updatedAt=new Date()] - Last update timestamp
35
+ */
36
+ constructor({
37
+ sessionId,
38
+ userId,
39
+ entityType,
40
+ currentStep = 1,
41
+ maxSteps,
42
+ stepData = {},
43
+ expiresAt,
44
+ completed = false,
45
+ createdAt = new Date(),
46
+ updatedAt = new Date(),
47
+ }) {
48
+ this.sessionId = sessionId;
49
+ this.userId = userId;
50
+ this.entityType = entityType;
51
+ this.currentStep = currentStep;
52
+ this.maxSteps = maxSteps;
53
+ this.stepData = stepData;
54
+ this.expiresAt = expiresAt;
55
+ this.completed = completed;
56
+ this.createdAt = createdAt;
57
+ this.updatedAt = updatedAt;
58
+
59
+ this.validate();
60
+ }
61
+
62
+ /**
63
+ * Validate session state
64
+ *
65
+ * @throws {Error} If validation fails
66
+ */
67
+ validate() {
68
+ if (!this.sessionId) {
69
+ throw new Error('Session ID is required');
70
+ }
71
+ if (!this.userId) {
72
+ throw new Error('User ID is required');
73
+ }
74
+ if (!this.entityType) {
75
+ throw new Error('Entity type is required');
76
+ }
77
+ if (this.currentStep < 1) {
78
+ throw new Error('Step must be >= 1');
79
+ }
80
+ if (this.currentStep > this.maxSteps) {
81
+ throw new Error('Current step cannot exceed max steps');
82
+ }
83
+ if (this.expiresAt < new Date()) {
84
+ throw new Error('Session has expired');
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Advance to next step with new data
90
+ *
91
+ * @param {Object} newStepData - Data collected from current step
92
+ * @throws {Error} If session is already completed
93
+ */
94
+ advanceStep(newStepData) {
95
+ if (this.completed) {
96
+ throw new Error('Cannot advance completed session');
97
+ }
98
+
99
+ this.currentStep += 1;
100
+ this.stepData = { ...this.stepData, ...newStepData };
101
+ this.updatedAt = new Date();
102
+ }
103
+
104
+ /**
105
+ * Mark session as complete
106
+ */
107
+ markComplete() {
108
+ this.completed = true;
109
+ this.updatedAt = new Date();
110
+ }
111
+
112
+ /**
113
+ * Check if session has expired
114
+ *
115
+ * @returns {boolean} True if session is expired
116
+ */
117
+ isExpired() {
118
+ return this.expiresAt < new Date();
119
+ }
120
+
121
+ /**
122
+ * Check if session can advance to next step
123
+ *
124
+ * @returns {boolean} True if session can advance
125
+ */
126
+ canAdvance() {
127
+ return !this.completed && this.currentStep < this.maxSteps;
128
+ }
129
+ }
130
+
131
+ module.exports = { AuthorizationSession };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Domain Entities Index
3
+ * Export all domain entities for convenient importing
4
+ */
5
+
6
+ const { AuthorizationSession } = require('./AuthorizationSession');
7
+
8
+ module.exports = {
9
+ AuthorizationSession,
10
+ };
package/modules/module.js CHANGED
@@ -102,15 +102,10 @@ class Module extends Delegate {
102
102
  this.api,
103
103
  this.userId
104
104
  );
105
- const apiParams = this.apiParamsFromCredential(this.api);
106
-
107
- if (!apiParams.refresh_token && this.api.isRefreshable) {
108
- console.warn(
109
- `[Frigg] No refresh_token in apiParams for module ${this.name}.`
110
- );
111
- }
112
-
113
- Object.assign(credentialDetails.details, apiParams);
105
+ Object.assign(
106
+ credentialDetails.details,
107
+ this.apiParamsFromCredential(this.api)
108
+ );
114
109
  credentialDetails.details.authIsValid = true;
115
110
 
116
111
  const persisted = await this.credentialRepository.upsertCredential(
@@ -0,0 +1,53 @@
1
+ const {
2
+ AuthorizationSessionRepositoryPostgres,
3
+ } = require('./authorization-session-repository-postgres');
4
+
5
+ /**
6
+ * Authorization Session Repository Factory
7
+ * Creates the appropriate repository adapter based on database type
8
+ *
9
+ * Database-specific implementations:
10
+ * - MongoDB: Uses String IDs (ObjectId), TTL index for auto-cleanup
11
+ * - PostgreSQL: Uses Int IDs, manual cleanup via deleteExpired()
12
+ *
13
+ * All repository methods return AuthorizationSession domain entities,
14
+ * ensuring application layer consistency regardless of database type.
15
+ *
16
+ * Environment Configuration:
17
+ * - DB_TYPE=mongodb (default) - Uses MongoDB adapter
18
+ * - DB_TYPE=postgresql - Uses PostgreSQL adapter
19
+ *
20
+ * @example
21
+ * ```javascript
22
+ * const repository = createAuthorizationSessionRepository();
23
+ * const session = await repository.findBySessionId(sessionId);
24
+ * ```
25
+ *
26
+ * @param {Object} [prismaClient] - Optional Prisma client for testing
27
+ * @returns {AuthorizationSessionRepositoryInterface} Configured repository adapter
28
+ * @throws {Error} If DB_TYPE is not supported
29
+ */
30
+ function createAuthorizationSessionRepository(prismaClient) {
31
+ const dbType = process.env.DB_TYPE || 'mongodb';
32
+
33
+ switch (dbType) {
34
+ case 'mongodb':
35
+ const { AuthorizationSessionRepositoryMongo } = require('./authorization-session-repository-mongo');
36
+ return new AuthorizationSessionRepositoryMongo(prismaClient);
37
+
38
+ case 'postgresql':
39
+ return new AuthorizationSessionRepositoryPostgres(prismaClient);
40
+
41
+ default:
42
+ throw new Error(
43
+ `Unsupported DB_TYPE: ${dbType}. Supported values: 'mongodb', 'postgresql'`
44
+ );
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ createAuthorizationSessionRepository,
50
+ // Export adapters for direct testing
51
+ get AuthorizationSessionRepositoryMongo() { return require('./authorization-session-repository-mongo').AuthorizationSessionRepositoryMongo; },
52
+ AuthorizationSessionRepositoryPostgres,
53
+ };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Authorization Session Repository Interface
3
+ * Abstract base class defining the contract for AuthorizationSession persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters (MongoDB, PostgreSQL) implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * @abstract
11
+ */
12
+ class AuthorizationSessionRepositoryInterface {
13
+ /**
14
+ * Create a new authorization session
15
+ *
16
+ * @param {import('../domain/entities/AuthorizationSession').AuthorizationSession} session - Session entity to create
17
+ * @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession>} Created session
18
+ * @abstract
19
+ */
20
+ async create(session) {
21
+ throw new Error('Method create must be implemented by subclass');
22
+ }
23
+
24
+ /**
25
+ * Find session by session ID
26
+ *
27
+ * @param {string} sessionId - Unique session identifier
28
+ * @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession|null>} Session entity or null if not found/expired
29
+ * @abstract
30
+ */
31
+ async findBySessionId(sessionId) {
32
+ throw new Error(
33
+ 'Method findBySessionId must be implemented by subclass'
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Find active session for user and entity type
39
+ * Returns the most recent active (non-completed, non-expired) session
40
+ *
41
+ * @param {string} userId - User ID
42
+ * @param {string} entityType - Entity type (module name)
43
+ * @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession|null>} Session entity or null if not found
44
+ * @abstract
45
+ */
46
+ async findActiveSession(userId, entityType) {
47
+ throw new Error(
48
+ 'Method findActiveSession must be implemented by subclass'
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Update existing session
54
+ *
55
+ * @param {import('../domain/entities/AuthorizationSession').AuthorizationSession} session - Session entity with updated data
56
+ * @returns {Promise<import('../domain/entities/AuthorizationSession').AuthorizationSession>} Updated session
57
+ * @abstract
58
+ */
59
+ async update(session) {
60
+ throw new Error('Method update must be implemented by subclass');
61
+ }
62
+
63
+ /**
64
+ * Delete expired sessions (cleanup operation)
65
+ * Should be called periodically to remove old sessions
66
+ *
67
+ * @returns {Promise<number>} Number of deleted sessions
68
+ * @abstract
69
+ */
70
+ async deleteExpired() {
71
+ throw new Error('Method deleteExpired must be implemented by subclass');
72
+ }
73
+ }
74
+
75
+ module.exports = { AuthorizationSessionRepositoryInterface };