@friggframework/core 2.0.0-next.40 → 2.0.0-next.42

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 (196) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +931 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +160 -0
  7. package/application/commands/integration-commands.test.js +123 -0
  8. package/application/commands/user-commands.js +213 -0
  9. package/application/index.js +69 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/create-handler.js +0 -6
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +301 -0
  15. package/credential/repositories/credential-repository-postgres.js +307 -0
  16. package/credential/repositories/credential-repository.js +307 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/config.js +117 -0
  20. package/database/encryption/README.md +683 -0
  21. package/database/encryption/encryption-integration.test.js +553 -0
  22. package/database/encryption/encryption-schema-registry.js +141 -0
  23. package/database/encryption/encryption-schema-registry.test.js +392 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/field-encryption-service.test.js +525 -0
  26. package/database/encryption/logger.js +79 -0
  27. package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
  28. package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
  29. package/database/encryption/postgres-relation-decryption.test.js +245 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/encryption/prisma-encryption-extension.test.js +439 -0
  32. package/database/index.js +25 -12
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +162 -0
  35. package/database/repositories/health-check-repository-factory.js +38 -0
  36. package/database/repositories/health-check-repository-interface.js +86 -0
  37. package/database/repositories/health-check-repository-mongodb.js +72 -0
  38. package/database/repositories/health-check-repository-postgres.js +75 -0
  39. package/database/repositories/health-check-repository.js +108 -0
  40. package/database/use-cases/check-database-health-use-case.js +34 -0
  41. package/database/use-cases/check-encryption-health-use-case.js +82 -0
  42. package/database/use-cases/test-encryption-use-case.js +252 -0
  43. package/encrypt/Cryptor.js +20 -152
  44. package/encrypt/index.js +1 -2
  45. package/encrypt/test-encrypt.js +0 -2
  46. package/handlers/app-definition-loader.js +38 -0
  47. package/handlers/app-handler-helpers.js +0 -3
  48. package/handlers/auth-flow.integration.test.js +147 -0
  49. package/handlers/backend-utils.js +25 -45
  50. package/handlers/integration-event-dispatcher.js +54 -0
  51. package/handlers/integration-event-dispatcher.test.js +141 -0
  52. package/handlers/routers/HEALTHCHECK.md +103 -1
  53. package/handlers/routers/auth.js +3 -14
  54. package/handlers/routers/health.js +63 -424
  55. package/handlers/routers/health.test.js +7 -0
  56. package/handlers/routers/integration-defined-routers.js +8 -5
  57. package/handlers/routers/user.js +25 -5
  58. package/handlers/routers/websocket.js +5 -3
  59. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  60. package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
  61. package/handlers/workers/integration-defined-workers.js +6 -3
  62. package/index.js +45 -22
  63. package/integrations/index.js +12 -10
  64. package/integrations/integration-base.js +224 -53
  65. package/integrations/integration-router.js +386 -178
  66. package/integrations/options.js +1 -1
  67. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  68. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  71. package/integrations/repositories/integration-mapping-repository.js +156 -0
  72. package/integrations/repositories/integration-repository-factory.js +44 -0
  73. package/integrations/repositories/integration-repository-interface.js +115 -0
  74. package/integrations/repositories/integration-repository-mongo.js +271 -0
  75. package/integrations/repositories/integration-repository-postgres.js +319 -0
  76. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  77. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  78. package/integrations/tests/use-cases/create-integration.test.js +131 -0
  79. package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
  80. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
  81. package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
  82. package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
  83. package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
  84. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  85. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  86. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  87. package/integrations/tests/use-cases/update-integration.test.js +141 -0
  88. package/integrations/use-cases/create-integration.js +83 -0
  89. package/integrations/use-cases/delete-integration-for-user.js +73 -0
  90. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  91. package/integrations/use-cases/get-integration-for-user.js +78 -0
  92. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  93. package/integrations/use-cases/get-integration-instance.js +83 -0
  94. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  95. package/integrations/use-cases/get-possible-integrations.js +27 -0
  96. package/integrations/use-cases/index.js +11 -0
  97. package/integrations/use-cases/load-integration-context-full.test.js +329 -0
  98. package/integrations/use-cases/load-integration-context.js +71 -0
  99. package/integrations/use-cases/load-integration-context.test.js +114 -0
  100. package/integrations/use-cases/update-integration-messages.js +44 -0
  101. package/integrations/use-cases/update-integration-status.js +32 -0
  102. package/integrations/use-cases/update-integration.js +93 -0
  103. package/integrations/utils/map-integration-dto.js +36 -0
  104. package/jest-global-setup-noop.js +3 -0
  105. package/jest-global-teardown-noop.js +3 -0
  106. package/{module-plugin → modules}/entity.js +1 -0
  107. package/{module-plugin → modules}/index.js +0 -8
  108. package/modules/module-factory.js +56 -0
  109. package/modules/module-hydration.test.js +205 -0
  110. package/modules/module.js +221 -0
  111. package/modules/repositories/module-repository-factory.js +33 -0
  112. package/modules/repositories/module-repository-interface.js +129 -0
  113. package/modules/repositories/module-repository-mongo.js +386 -0
  114. package/modules/repositories/module-repository-postgres.js +437 -0
  115. package/modules/repositories/module-repository.js +327 -0
  116. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  117. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  118. package/modules/tests/doubles/test-module-factory.js +16 -0
  119. package/modules/tests/doubles/test-module-repository.js +39 -0
  120. package/modules/use-cases/get-entities-for-user.js +32 -0
  121. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  122. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  123. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  124. package/modules/use-cases/get-module.js +56 -0
  125. package/modules/use-cases/process-authorization-callback.js +121 -0
  126. package/modules/use-cases/refresh-entity-options.js +59 -0
  127. package/modules/use-cases/test-module-auth.js +55 -0
  128. package/modules/utils/map-module-dto.js +18 -0
  129. package/package.json +14 -6
  130. package/prisma-mongodb/schema.prisma +321 -0
  131. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  132. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  133. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  134. package/prisma-postgresql/schema.prisma +303 -0
  135. package/syncs/manager.js +468 -443
  136. package/syncs/repositories/sync-repository-factory.js +38 -0
  137. package/syncs/repositories/sync-repository-interface.js +109 -0
  138. package/syncs/repositories/sync-repository-mongo.js +239 -0
  139. package/syncs/repositories/sync-repository-postgres.js +319 -0
  140. package/syncs/sync.js +0 -1
  141. package/token/repositories/token-repository-factory.js +33 -0
  142. package/token/repositories/token-repository-interface.js +131 -0
  143. package/token/repositories/token-repository-mongo.js +212 -0
  144. package/token/repositories/token-repository-postgres.js +257 -0
  145. package/token/repositories/token-repository.js +219 -0
  146. package/types/integrations/index.d.ts +2 -6
  147. package/types/module-plugin/index.d.ts +5 -57
  148. package/types/syncs/index.d.ts +0 -2
  149. package/user/repositories/user-repository-factory.js +46 -0
  150. package/user/repositories/user-repository-interface.js +198 -0
  151. package/user/repositories/user-repository-mongo.js +250 -0
  152. package/user/repositories/user-repository-postgres.js +311 -0
  153. package/user/tests/doubles/test-user-repository.js +72 -0
  154. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  155. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  156. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  157. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  158. package/user/tests/use-cases/login-user.test.js +140 -0
  159. package/user/use-cases/create-individual-user.js +61 -0
  160. package/user/use-cases/create-organization-user.js +47 -0
  161. package/user/use-cases/create-token-for-user-id.js +30 -0
  162. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  163. package/user/use-cases/login-user.js +122 -0
  164. package/user/user.js +77 -0
  165. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  166. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  167. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  168. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  169. package/websocket/repositories/websocket-connection-repository.js +160 -0
  170. package/database/models/State.js +0 -9
  171. package/database/models/Token.js +0 -70
  172. package/database/mongo.js +0 -171
  173. package/encrypt/Cryptor.test.js +0 -32
  174. package/encrypt/encrypt.js +0 -104
  175. package/encrypt/encrypt.test.js +0 -1069
  176. package/handlers/routers/middleware/loadUser.js +0 -15
  177. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  178. package/integrations/create-frigg-backend.js +0 -31
  179. package/integrations/integration-factory.js +0 -251
  180. package/integrations/integration-mapping.js +0 -43
  181. package/integrations/integration-model.js +0 -46
  182. package/integrations/integration-user.js +0 -144
  183. package/integrations/test/integration-base.test.js +0 -144
  184. package/module-plugin/auther.js +0 -393
  185. package/module-plugin/credential.js +0 -22
  186. package/module-plugin/entity-manager.js +0 -70
  187. package/module-plugin/manager.js +0 -169
  188. package/module-plugin/module-factory.js +0 -61
  189. package/module-plugin/test/auther.test.js +0 -97
  190. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  191. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  192. /package/{module-plugin → modules}/requester/basic.js +0 -0
  193. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  194. /package/{module-plugin → modules}/requester/requester.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  196. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
package/README.md CHANGED
@@ -1,83 +1,964 @@
1
1
  # Frigg Core
2
2
 
3
- The `frigg-core` package is the heart of the Frigg Framework. It contains the core functionality and essential modules required to build and maintain integrations at scale.
4
-
3
+ The `@friggframework/core` package is the foundational layer of the Frigg Framework, implementing a hexagonal architecture pattern for building scalable, maintainable enterprise integrations. It provides the essential building blocks, domain logic, and infrastructure components that power the entire Frigg ecosystem.
5
4
 
6
5
  ## Table of Contents
7
6
 
8
- - [Introduction](#introduction)
9
- - [Features](#features)
7
+ - [Architecture Overview](#architecture-overview)
10
8
  - [Installation](#installation)
11
- - [Usage](#usage)
12
- - [Modules](#modules)
9
+ - [Quick Start](#quick-start)
10
+ - [Core Components](#core-components)
11
+ - [Hexagonal Architecture](#hexagonal-architecture)
12
+ - [Usage Examples](#usage-examples)
13
+ - [Testing](#testing)
14
+ - [Development](#development)
15
+ - [API Reference](#api-reference)
13
16
  - [Contributing](#contributing)
14
- - [License](#license)
15
-
16
- ## Introduction
17
17
 
18
- The Frigg Core package provides the foundational components and utilities for the Frigg Framework. It is designed to be modular, extensible, and easy to integrate with other packages in the Frigg ecosystem.
18
+ ## Architecture Overview
19
19
 
20
- ## Features
20
+ Frigg Core implements a **hexagonal architecture** (also known as ports and adapters) that separates business logic from external concerns:
21
21
 
22
- - **Associations**: Manage relationships between different entities.
23
- - **Database**: Database utilities and connectors.
24
- - **Encryption**: Secure data encryption and decryption.
25
- - **Error Handling**: Standardized error handling mechanisms.
26
- - **Integrations**: Tools for building and managing integrations.
27
- - **Lambda**: Utilities for AWS Lambda functions.
28
- - **Logging**: Structured logging utilities.
29
- - **Module Plugin**: Plugin system for extending core functionality.
30
- - **Syncs**: Synchronization utilities for data consistency.
31
- - **Infrastructure**: Frigg reads through your integration definitions and auto-generates the infrastructure your code needs to run smoothly.
22
+ ```
23
+ ┌─────────────────────────────────────────────────────────────┐
24
+ │ Inbound Adapters │
25
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
26
+ │ │ Express │ │ Lambda │ │ WebSocket │ │
27
+ │ │ Routes │ │ Handlers │ │ Handlers │ │
28
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
29
+ └─────────────────────────────────────────────────────────────┘
30
+
31
+ ┌─────────────────────────────────────────────────────────────┐
32
+ │ Application Layer │
33
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
34
+ │ │ Use Cases │ │ Services │ │ Coordinators│ │
35
+ │ │ (Business │ │ │ │ │ │
36
+ │ │ Logic) │ │ │ │ │ │
37
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
38
+ └─────────────────────────────────────────────────────────────┘
39
+
40
+ ┌─────────────────────────────────────────────────────────────┐
41
+ │ Domain Layer │
42
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
43
+ │ │ Integration │ │ Entities │ │ Value │ │
44
+ │ │ Aggregates │ │ │ │ Objects │ │
45
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
46
+ └─────────────────────────────────────────────────────────────┘
47
+
48
+ ┌─────────────────────────────────────────────────────────────┐
49
+ │ Outbound Adapters │
50
+ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
51
+ │ │ Database │ │ API Modules │ │ Event │ │
52
+ │ │ Repositories│ │ │ │ Publishers │ │
53
+ │ └─────────────┘ └─────────────┘ └─────────────┘ │
54
+ └─────────────────────────────────────────────────────────────┘
55
+ ```
32
56
 
33
57
  ## Installation
34
58
 
35
- To install the `frigg-core` package, use npm or yarn:
36
-
37
- ```sh
59
+ ```bash
38
60
  npm install @friggframework/core
39
61
  # or
40
62
  yarn add @friggframework/core
41
63
  ```
42
- ## Usage
43
- Here's a basic example of how to use the frigg-core package:
64
+
65
+ ### Prerequisites
66
+
67
+ - Node.js 16+
68
+ - MongoDB 4.4+ (for data persistence)
69
+ - AWS credentials (for SQS, KMS, Lambda deployment)
70
+
71
+ ### Environment Variables
72
+
73
+ ```bash
74
+ # Database
75
+ MONGO_URI=mongodb://localhost:27017/frigg
76
+ FRIGG_ENCRYPTION_KEY=your-256-bit-encryption-key
77
+
78
+ # AWS (Optional - for production deployments)
79
+ AWS_REGION=us-east-1
80
+ AWS_ACCESS_KEY_ID=your-access-key
81
+ AWS_SECRET_ACCESS_KEY=your-secret-key
82
+
83
+ # Logging
84
+ DEBUG=frigg:*
85
+ LOG_LEVEL=info
86
+ ```
87
+
88
+ ## Core Components
89
+
90
+ ### 1. Integrations (`/integrations`)
91
+
92
+ The heart of the framework - manages integration lifecycle and business logic.
93
+
94
+ **Key Classes:**
95
+ - `IntegrationBase` - Base class for all integrations
96
+ - `Integration` - Domain aggregate using Proxy pattern
97
+ - Use cases: `CreateIntegration`, `UpdateIntegration`, `DeleteIntegration`
98
+
99
+ **Usage:**
44
100
  ```javascript
45
- const { encrypt, decrypt } = require('@friggframework/core/encrypt');
46
- const { logInfo } = require('@friggframework/core/logs');
101
+ const { IntegrationBase } = require('@friggframework/core');
47
102
 
48
- const secret = 'mySecret';
49
- const encrypted = encrypt(secret);
50
- const decrypted = decrypt(encrypted);
103
+ class SlackHubSpotSync extends IntegrationBase {
104
+ static Definition = {
105
+ name: 'slack-hubspot-sync',
106
+ version: '2.1.0',
107
+ modules: {
108
+ slack: 'slack',
109
+ hubspot: 'hubspot'
110
+ }
111
+ };
51
112
 
52
- logInfo(`Encrypted: ${encrypted}`);
53
- logInfo(`Decrypted: ${decrypted}`);
113
+ async onCreate({ integrationId }) {
114
+ // Setup webhooks, initial sync, etc.
115
+ await this.slack.createWebhook(process.env.WEBHOOK_URL);
116
+ await this.hubspot.setupContactSync();
117
+ await super.onCreate({ integrationId });
118
+ }
119
+ }
54
120
  ```
55
121
 
56
- ## Modules
122
+ ### 3. Database (`/database`)
123
+
124
+ MongoDB integration with Mongoose ODM.
57
125
 
58
- The frigg-core package is organized into several modules:
126
+ **Key Components:**
127
+ - Connection management
128
+ - Pre-built models (User, Integration, Credential, etc.)
129
+ - Schema definitions
130
+
131
+ **Usage:**
132
+ ```javascript
133
+ const {
134
+ connectToDatabase,
135
+ IntegrationModel,
136
+ UserModel
137
+ } = require('@friggframework/core');
138
+
139
+ await connectToDatabase();
140
+
141
+ // Query integrations
142
+ const userIntegrations = await IntegrationModel.find({
143
+ userId: 'user-123',
144
+ status: 'ENABLED'
145
+ });
146
+
147
+ // Create user
148
+ const user = new UserModel({
149
+ email: 'user@example.com',
150
+ name: 'John Doe'
151
+ });
152
+ await user.save();
153
+ ```
154
+
155
+ ### 4. Encryption (`/encrypt`)
156
+
157
+ AES-256-GCM encryption for sensitive data.
158
+
159
+ **Usage:**
160
+ ```javascript
161
+ const { Encrypt, Cryptor } = require('@friggframework/core');
162
+
163
+ // Simple encryption
164
+ const encrypted = Encrypt.encrypt('sensitive-data');
165
+ const decrypted = Encrypt.decrypt(encrypted);
166
+
167
+ // Advanced encryption with custom key
168
+ const cryptor = new Cryptor(process.env.CUSTOM_KEY);
169
+ const secureData = cryptor.encrypt(JSON.stringify({
170
+ accessToken: 'oauth-token',
171
+ refreshToken: 'refresh-token'
172
+ }));
173
+ ```
174
+
175
+ ### 5. Error Handling (`/errors`)
176
+
177
+ Standardized error types with proper HTTP status codes.
178
+
179
+ **Usage:**
180
+ ```javascript
181
+ const {
182
+ BaseError,
183
+ RequiredPropertyError,
184
+ FetchError
185
+ } = require('@friggframework/core');
186
+
187
+ // Custom business logic error
188
+ throw new RequiredPropertyError('userId is required');
189
+
190
+ // API communication error
191
+ throw new FetchError('Failed to fetch data from external API', {
192
+ statusCode: 404,
193
+ response: errorResponse
194
+ });
195
+
196
+ // Base error with custom properties
197
+ throw new BaseError('Integration failed', {
198
+ integrationId: 'int-123',
199
+ errorCode: 'SYNC_FAILED'
200
+ });
201
+ ```
202
+
203
+ ### 6. Logging (`/logs`)
204
+
205
+ Structured logging with debug capabilities.
206
+
207
+ **Usage:**
208
+ ```javascript
209
+ const { debug, initDebugLog, flushDebugLog } = require('@friggframework/core');
210
+
211
+ // Initialize debug logging
212
+ initDebugLog('integration:slack');
213
+
214
+ // Log debug information
215
+ debug('Processing webhook payload', {
216
+ eventType: 'contact.created',
217
+ payload: webhookData
218
+ });
219
+
220
+ // Flush logs (useful in serverless environments)
221
+ await flushDebugLog();
222
+ ```
59
223
 
60
- - **Associations**: @friggframework/core/associations
61
- - **Database**: @friggframework/core/database
62
- - **Encryption**: @friggframework/core/encrypt
63
- - **Errors**: @friggframework/core/errors
64
- - **Integrations**: @friggframework/core/integrations
65
- - **Lambda**: @friggframework/core/lambda
66
- - **Logs**: @friggframework/core/logs
67
- - **Module Plugin**: @friggframework/core/module-plugin
68
- - **Syncs**: @friggframework/core/syncs
69
- - **Infrastructure**: @friggframework/core/infrastructure
224
+ ### 7. User Management (`/user`)
70
225
 
226
+ Comprehensive user authentication and authorization system supporting both individual and organizational users.
71
227
 
72
- Each module provides specific functionality and can be imported individually as needed.
228
+ **Key Classes:**
229
+ - `User` - Domain aggregate for user entities
230
+ - `UserRepository` - Data access for user operations
231
+ - Use cases: `LoginUser`, `CreateIndividualUser`, `CreateOrganizationUser`, `GetUserFromBearerToken`
73
232
 
74
- ## Contributing
233
+ **User Types:**
234
+ - **Individual Users**: Personal accounts with email/username authentication
235
+ - **Organization Users**: Business accounts with organization-level access
236
+ - **Hybrid Mode**: Support for both user types simultaneously
237
+
238
+ **Authentication Methods:**
239
+ - **Password-based**: Traditional username/password authentication
240
+ - **Token-based**: Bearer token authentication with session management
241
+ - **App-based**: External app user ID authentication (passwordless)
242
+
243
+ **Usage:**
244
+ ```javascript
245
+ const {
246
+ LoginUser,
247
+ CreateIndividualUser,
248
+ GetUserFromBearerToken,
249
+ UserRepository
250
+ } = require('@friggframework/core');
251
+
252
+ // Configure user behavior in app definition
253
+ const userConfig = {
254
+ usePassword: true,
255
+ primary: 'individual', // or 'organization'
256
+ individualUserRequired: true,
257
+ organizationUserRequired: false
258
+ };
259
+
260
+ const userRepository = new UserRepository({ userConfig });
261
+
262
+ // Create individual user
263
+ const createUser = new CreateIndividualUser({ userRepository, userConfig });
264
+ const user = await createUser.execute({
265
+ email: 'user@example.com',
266
+ username: 'john_doe',
267
+ password: 'secure_password',
268
+ appUserId: 'external_user_123' // Optional external reference
269
+ });
270
+
271
+ // Login user
272
+ const loginUser = new LoginUser({ userRepository, userConfig });
273
+ const authenticatedUser = await loginUser.execute({
274
+ username: 'john_doe',
275
+ password: 'secure_password'
276
+ });
277
+
278
+ // Token-based authentication
279
+ const getUserFromToken = new GetUserFromBearerToken({ userRepository, userConfig });
280
+ const user = await getUserFromToken.execute('Bearer eyJhbGciOiJIUzI1NiIs...');
281
+
282
+ // Access user properties
283
+ console.log('User ID:', user.getId());
284
+ console.log('Primary user:', user.getPrimaryUser());
285
+ console.log('Individual user:', user.getIndividualUser());
286
+ console.log('Organization user:', user.getOrganizationUser());
287
+ ```
288
+
289
+ ### 8. Lambda Utilities (`/lambda`)
290
+
291
+ AWS Lambda-specific utilities and helpers.
292
+
293
+ **Usage:**
294
+ ```javascript
295
+ const { TimeoutCatcher } = require('@friggframework/core');
296
+
297
+ exports.handler = async (event, context) => {
298
+ const timeoutCatcher = new TimeoutCatcher(context);
299
+
300
+ try {
301
+ // Long-running integration process
302
+ const result = await processIntegrationSync(event);
303
+ return { statusCode: 200, body: JSON.stringify(result) };
304
+ } catch (error) {
305
+ if (timeoutCatcher.isNearTimeout()) {
306
+ // Handle graceful shutdown
307
+ await saveProgressState(event);
308
+ return { statusCode: 202, body: 'Processing continues...' };
309
+ }
310
+ throw error;
311
+ }
312
+ };
313
+ ```
314
+
315
+ ## User Management & Behavior
316
+
317
+ Frigg Core provides a flexible user management system that supports various authentication patterns and user types. The system is designed around the concept of **Individual Users** (personal accounts) and **Organization Users** (business accounts), with configurable authentication methods.
318
+
319
+ ### User Configuration
320
+
321
+ User behavior is configured in the app definition, allowing you to customize authentication requirements:
322
+
323
+ ```javascript
324
+ // App Definition with User Configuration
325
+ const appDefinition = {
326
+ integrations: [HubSpotIntegration],
327
+ user: {
328
+ usePassword: true, // Enable password authentication
329
+ primary: 'individual', // Primary user type: 'individual' or 'organization'
330
+ organizationUserRequired: true, // Require organization user
331
+ individualUserRequired: true, // Require individual user
332
+ }
333
+ };
334
+ ```
335
+
336
+ ### User Domain Model
337
+
338
+ The `User` class provides a rich domain model with behavior:
339
+
340
+ ```javascript
341
+ const { User } = require('@friggframework/core');
342
+
343
+ // User instance methods
344
+ const user = new User(individualUser, organizationUser, usePassword, primary);
345
+
346
+ // Access methods
347
+ user.getId() // Get primary user ID
348
+ user.getPrimaryUser() // Get primary user based on config
349
+ user.getIndividualUser() // Get individual user
350
+ user.getOrganizationUser() // Get organization user
351
+
352
+ // Validation methods
353
+ user.isPasswordRequired() // Check if password is required
354
+ user.isPasswordValid(password) // Validate password
355
+ user.isIndividualUserRequired() // Check individual user requirement
356
+ user.isOrganizationUserRequired() // Check organization user requirement
357
+
358
+ // Configuration methods
359
+ user.setIndividualUser(individualUser)
360
+ user.setOrganizationUser(organizationUser)
361
+ ```
362
+
363
+ ### Database Models
364
+
365
+ The user system uses MongoDB with Mongoose for data persistence:
366
+
367
+ ```javascript
368
+ // Individual User Schema
369
+ {
370
+ email: String,
371
+ username: { type: String, unique: true },
372
+ hashword: String, // Encrypted password
373
+ appUserId: String, // External app reference
374
+ organizationUser: ObjectId // Reference to organization
375
+ }
376
+
377
+ // Organization User Schema
378
+ {
379
+ name: String,
380
+ appOrgId: String, // External organization reference
381
+ domain: String,
382
+ settings: Object
383
+ }
384
+
385
+ // Session Token Schema
386
+ {
387
+ user: ObjectId, // Reference to user
388
+ token: String, // Encrypted token
389
+ expires: Date,
390
+ created: Date
391
+ }
392
+ ```
393
+
394
+ ### Security Features
395
+
396
+ - **Password Hashing**: Uses bcrypt with configurable salt rounds
397
+ - **Token Management**: Secure session tokens with expiration
398
+ - **Unique Constraints**: Enforced username and email uniqueness
399
+ - **External References**: Support for external app user/org IDs
400
+ - **Flexible Authentication**: Multiple authentication methods
401
+
402
+ ## Hexagonal Architecture
403
+
404
+ ### Use Case Pattern
405
+
406
+ Each business operation is encapsulated in a use case class:
407
+
408
+ ```javascript
409
+ class UpdateIntegrationStatus {
410
+ constructor({ integrationRepository }) {
411
+ this.integrationRepository = integrationRepository;
412
+ }
75
413
 
76
- We welcome contributions from the community! Please read our contributing guide to get started. Make sure to follow our code of conduct and use the provided pull request template.
414
+ async execute(integrationId, newStatus) {
415
+ // Business logic validation
416
+ if (!['ENABLED', 'DISABLED', 'ERROR'].includes(newStatus)) {
417
+ throw new Error('Invalid status');
418
+ }
419
+
420
+ // Domain operation
421
+ const integration = await this.integrationRepository.findById(integrationId);
422
+ if (!integration) {
423
+ throw new Error('Integration not found');
424
+ }
425
+
426
+ // Update and persist
427
+ integration.status = newStatus;
428
+ integration.updatedAt = new Date();
429
+
430
+ return await this.integrationRepository.save(integration);
431
+ }
432
+ }
433
+ ```
434
+
435
+ ### Repository Pattern
436
+
437
+ Data access is abstracted through repositories:
438
+
439
+ ```javascript
440
+ class IntegrationRepository {
441
+ async findById(id) {
442
+ return await IntegrationModel.findById(id);
443
+ }
444
+
445
+ async findByUserId(userId) {
446
+ return await IntegrationModel.find({ userId, deletedAt: null });
447
+ }
448
+
449
+ async save(integration) {
450
+ return await integration.save();
451
+ }
452
+
453
+ async createIntegration(entities, userId, config) {
454
+ const integration = new IntegrationModel({
455
+ entitiesIds: entities,
456
+ userId,
457
+ config,
458
+ status: 'NEW',
459
+ createdAt: new Date()
460
+ });
461
+ return await integration.save();
462
+ }
463
+ }
464
+ ```
465
+
466
+ ### Domain Aggregates
467
+
468
+ Complex business objects with behavior:
469
+
470
+ ```javascript
471
+ const Integration = new Proxy(class {}, {
472
+ construct(target, args) {
473
+ const [params] = args;
474
+ const instance = new params.integrationClass(params);
475
+
476
+ // Attach domain properties
477
+ Object.assign(instance, {
478
+ id: params.id,
479
+ userId: params.userId,
480
+ entities: params.entities,
481
+ config: params.config,
482
+ status: params.status,
483
+ modules: params.modules
484
+ });
485
+
486
+ return instance;
487
+ }
488
+ });
489
+ ```
490
+
491
+ ## Usage Examples
492
+
493
+ ### Real-World HubSpot Integration Example
494
+
495
+ Here's a complete, production-ready HubSpot integration that demonstrates advanced Frigg features:
496
+
497
+ ```javascript
498
+ const {
499
+ get,
500
+ IntegrationBase,
501
+ WebsocketConnection,
502
+ } = require('@friggframework/core');
503
+ const FriggConstants = require('../utils/constants');
504
+ const hubspot = require('@friggframework/api-module-hubspot');
505
+ const testRouter = require('../testRouter');
506
+ const extensions = require('../extensions');
507
+
508
+ class HubSpotIntegration extends IntegrationBase {
509
+ static Definition = {
510
+ name: 'hubspot',
511
+ version: '1.0.0',
512
+ supportedVersions: ['1.0.0'],
513
+ hasUserConfig: true,
514
+
515
+ display: {
516
+ label: 'HubSpot',
517
+ description: hubspot.Config.description,
518
+ category: 'Sales & CRM, Marketing',
519
+ detailsUrl: 'https://hubspot.com',
520
+ icon: hubspot.Config.logoUrl,
521
+ },
522
+ modules: {
523
+ hubspot: {
524
+ definition: hubspot.Definition,
525
+ },
526
+ },
527
+ // Express routes for webhook endpoints and custom APIs
528
+ routes: [
529
+ {
530
+ path: '/hubspot/webhooks',
531
+ method: 'POST',
532
+ event: 'HUBSPOT_WEBHOOK',
533
+ },
534
+ testRouter,
535
+ ],
536
+ };
537
+
538
+ constructor() {
539
+ super();
540
+
541
+ // Define event handlers for various integration actions
542
+ this.events = {
543
+ // Webhook handler with real-time WebSocket broadcasting
544
+ HUBSPOT_WEBHOOK: {
545
+ handler: async ({ data, context }) => {
546
+ console.log('Received HubSpot webhook:', data);
547
+
548
+ // Broadcast to all connected WebSocket clients
549
+ const activeConnections = await WebsocketConnection.getActiveConnections();
550
+ const message = JSON.stringify({
551
+ type: 'HUBSPOT_WEBHOOK',
552
+ data,
553
+ });
554
+
555
+ activeConnections.forEach((connection) => {
556
+ connection.send(message);
557
+ });
558
+ },
559
+ },
560
+
561
+ // User action: Get sample data with formatted table output
562
+ [FriggConstants.defaultEvents.GET_SAMPLE_DATA]: {
563
+ type: FriggConstants.eventTypes.USER_ACTION,
564
+ handler: this.getSampleData,
565
+ title: 'Get Sample Data',
566
+ description: 'Get sample data from HubSpot and display in a formatted table',
567
+ userActionType: 'QUICK_ACTION',
568
+ },
569
+
570
+ // User action: List available objects
571
+ GET_OBJECT_LIST: {
572
+ type: FriggConstants.eventTypes.USER_ACTION,
573
+ handler: this.getObjectList,
574
+ title: 'Get Object List',
575
+ description: 'Get list of available HubSpot objects',
576
+ userActionType: 'DATA',
577
+ },
578
+
579
+ // User action: Create records with dynamic forms
580
+ CREATE_RECORD: {
581
+ type: FriggConstants.eventTypes.USER_ACTION,
582
+ handler: this.createRecord,
583
+ title: 'Create Record',
584
+ description: 'Create a new record in HubSpot',
585
+ userActionType: 'DATA',
586
+ },
587
+ };
588
+
589
+ // Extension system for modular functionality
590
+ this.extensions = {
591
+ hubspotWebhooks: {
592
+ extension: extensions.hubspotWebhooks,
593
+ handlers: {
594
+ WEBHOOK_EVENT: this.handleWebhookEvent,
595
+ },
596
+ },
597
+ };
598
+ }
599
+
600
+ // Business logic: Fetch and format sample data
601
+ async getSampleData({ objectName }) {
602
+ let res;
603
+ switch (objectName) {
604
+ case 'deals':
605
+ res = await this.hubspot.api.searchDeals({
606
+ properties: ['dealname,amount,closedate'],
607
+ });
608
+ break;
609
+ case 'contacts':
610
+ res = await this.hubspot.api.listContacts({
611
+ after: 0,
612
+ properties: 'firstname,lastname,email',
613
+ });
614
+ break;
615
+ case 'companies':
616
+ res = await this.hubspot.api.searchCompanies({
617
+ properties: ['name,website,email'],
618
+ limit: 100,
619
+ });
620
+ break;
621
+ default:
622
+ throw new Error(`Unsupported object type: ${objectName}`);
623
+ }
624
+
625
+ const portalId = this.hubspot.entity.externalId;
626
+
627
+ // Format data with HubSpot record links
628
+ const formatted = res.results.map((item) => {
629
+ const formattedItem = {
630
+ linkToRecord: `https://app.hubspot.com/contacts/${portalId}/${objectName}/${item.id}/`,
631
+ id: item.id,
632
+ };
633
+
634
+ // Clean and format properties
635
+ for (const [key, value] of Object.entries(item.properties)) {
636
+ if (value !== null && value !== undefined && value !== '') {
637
+ formattedItem[key] = value;
638
+ }
639
+ }
640
+ delete formattedItem.hs_object_id;
641
+
642
+ return formattedItem;
643
+ });
644
+
645
+ return { label: objectName, data: formatted };
646
+ }
647
+
648
+ // Return available HubSpot object types
649
+ async getObjectList() {
650
+ return [
651
+ { key: 'deals', label: 'Deals' },
652
+ { key: 'contacts', label: 'Contacts' },
653
+ { key: 'companies', label: 'Companies' },
654
+ ];
655
+ }
656
+
657
+ // Create records based on object type
658
+ async createRecord(args) {
659
+ let res;
660
+ const objectType = args.objectType;
661
+ delete args.objectType;
662
+
663
+ switch (objectType.toLowerCase()) {
664
+ case 'deal':
665
+ res = await this.hubspot.api.createDeal({ ...args });
666
+ break;
667
+ case 'company':
668
+ res = await this.hubspot.api.createCompany({ ...args });
669
+ break;
670
+ case 'contact':
671
+ res = await this.hubspot.api.createContact({ ...args });
672
+ break;
673
+ default:
674
+ throw new Error(`Unsupported object type: ${objectType}`);
675
+ }
676
+ return { data: res };
677
+ }
678
+
679
+ // Dynamic form generation based on action and context
680
+ async getActionOptions({ actionId, data }) {
681
+ switch (actionId) {
682
+ case 'CREATE_RECORD':
683
+ let jsonSchema = {
684
+ type: 'object',
685
+ properties: {
686
+ objectType: {
687
+ type: 'string',
688
+ title: 'Object Type',
689
+ },
690
+ },
691
+ required: [],
692
+ };
693
+
694
+ let uiSchema = {
695
+ type: 'HorizontalLayout',
696
+ elements: [
697
+ {
698
+ type: 'Control',
699
+ scope: '#/properties/objectType',
700
+ rule: { effect: 'HIDE', condition: {} },
701
+ },
702
+ ],
703
+ };
704
+
705
+ // Generate form fields based on object type
706
+ switch (data.name.toLowerCase()) {
707
+ case 'deal':
708
+ jsonSchema.properties = {
709
+ ...jsonSchema.properties,
710
+ dealname: { type: 'string', title: 'Deal Name' },
711
+ amount: { type: 'number', title: 'Amount' },
712
+ };
713
+ jsonSchema.required = ['dealname', 'amount'];
714
+ uiSchema.elements.push(
715
+ { type: 'Control', scope: '#/properties/dealname' },
716
+ { type: 'Control', scope: '#/properties/amount' }
717
+ );
718
+ break;
719
+
720
+ case 'company':
721
+ jsonSchema.properties = {
722
+ ...jsonSchema.properties,
723
+ name: { type: 'string', title: 'Company Name' },
724
+ website: { type: 'string', title: 'Website URL' },
725
+ };
726
+ jsonSchema.required = ['name', 'website'];
727
+ uiSchema.elements.push(
728
+ { type: 'Control', scope: '#/properties/name' },
729
+ { type: 'Control', scope: '#/properties/website' }
730
+ );
731
+ break;
732
+
733
+ case 'contact':
734
+ jsonSchema.properties = {
735
+ ...jsonSchema.properties,
736
+ firstname: { type: 'string', title: 'First Name' },
737
+ lastname: { type: 'string', title: 'Last Name' },
738
+ email: { type: 'string', title: 'Email Address' },
739
+ };
740
+ jsonSchema.required = ['firstname', 'lastname', 'email'];
741
+ uiSchema.elements.push(
742
+ { type: 'Control', scope: '#/properties/firstname' },
743
+ { type: 'Control', scope: '#/properties/lastname' },
744
+ { type: 'Control', scope: '#/properties/email' }
745
+ );
746
+ break;
747
+
748
+ default:
749
+ throw new Error(`Unsupported object type: ${data.name}`);
750
+ }
751
+
752
+ return {
753
+ jsonSchema,
754
+ uiSchema,
755
+ data: { objectType: data.name },
756
+ };
757
+ }
758
+ return null;
759
+ }
760
+
761
+ async getConfigOptions() {
762
+ // Return configuration options for the integration
763
+ return {};
764
+ }
765
+ }
766
+
767
+ module.exports = HubSpotIntegration;
768
+ ```
769
+
770
+ index.js
771
+ ```js
772
+ const HubSpotIntegration = require('./src/integrations/HubSpotIntegration');
773
+
774
+ const appDefinition = {
775
+ integrations: [
776
+ HubSpotIntegration,
777
+ ],
778
+ user: {
779
+ usePassword: true,
780
+ primary: 'individual',
781
+ organizationUserRequired: true,
782
+ individualUserRequired: true,
783
+ }
784
+ }
785
+
786
+ module.exports = {
787
+ Definition: appDefinition,
788
+ }
789
+
790
+ ```
791
+
792
+
793
+ ### Key Features Demonstrated
794
+
795
+ This real-world example showcases:
796
+
797
+ **🔄 Webhook Integration**: Real-time event processing with WebSocket broadcasting
798
+ **📊 User Actions**: Interactive data operations with dynamic form generation
799
+ **🎯 API Module Integration**: Direct use of `@friggframework/api-module-hubspot`
800
+ **🛠 Extension System**: Modular functionality through extensions
801
+ **📝 Dynamic Forms**: JSON Schema-based form generation for different object types
802
+ **🔗 Deep Linking**: Direct links to HubSpot records in formatted data
803
+ **⚡ Real-time Updates**: WebSocket connections for live data streaming
804
+
805
+
806
+ ## Testing
807
+
808
+ ### Running Tests
809
+
810
+ ```bash
811
+ # Run all tests
812
+ npm test
813
+
814
+ # Run specific test file
815
+ npm test -- --testPathPattern="integration.test.js"
816
+ ```
817
+
818
+ ### Test Structure
819
+
820
+ The core package uses a comprehensive testing approach:
821
+
822
+ ```javascript
823
+ // Example test structure
824
+ describe('CreateIntegration Use-Case', () => {
825
+ let integrationRepository;
826
+ let moduleFactory;
827
+ let useCase;
828
+
829
+ beforeEach(() => {
830
+ integrationRepository = new TestIntegrationRepository();
831
+ moduleFactory = new TestModuleFactory();
832
+ useCase = new CreateIntegration({
833
+ integrationRepository,
834
+ integrationClasses: [TestIntegration],
835
+ moduleFactory
836
+ });
837
+ });
838
+
839
+ describe('happy path', () => {
840
+ it('creates an integration and returns DTO', async () => {
841
+ const result = await useCase.execute(['entity-1'], 'user-1', { type: 'test' });
842
+ expect(result.id).toBeDefined();
843
+ expect(result.status).toBe('NEW');
844
+ });
845
+ });
846
+
847
+ describe('error cases', () => {
848
+ it('throws error for unknown integration type', async () => {
849
+ await expect(useCase.execute(['entity-1'], 'user-1', { type: 'unknown' }))
850
+ .rejects.toThrow('No integration class found for type: unknown');
851
+ });
852
+ });
853
+ });
854
+ ```
855
+
856
+ ### Test Doubles
857
+
858
+ The framework provides test doubles for external dependencies:
859
+
860
+ ```javascript
861
+ const { TestIntegrationRepository, TestModuleFactory } = require('@friggframework/core/test');
862
+
863
+ // Mock repository for testing
864
+ const testRepo = new TestIntegrationRepository();
865
+ testRepo.addMockIntegration({ id: 'test-123', userId: 'user-1' });
866
+
867
+ // Mock module factory
868
+ const testFactory = new TestModuleFactory();
869
+ testFactory.addMockModule('hubspot', mockHubSpotModule);
870
+ ```
871
+
872
+ ## Development
873
+
874
+ ### Project Structure
875
+
876
+ ```
877
+ packages/core/
878
+ ├── integrations/ # Integration domain logic
879
+ │ ├── use-cases/ # Business use cases
880
+ │ ├── tests/ # Integration tests
881
+ │ └── integration-base.js # Base integration class
882
+ ├── modules/ # API module system
883
+ │ ├── requester/ # HTTP clients
884
+ │ └── use-cases/ # Module management
885
+ ├── database/ # Data persistence
886
+ ├── encrypt/ # Encryption utilities
887
+ ├── errors/ # Error definitions
888
+ ├── logs/ # Logging system
889
+ └── lambda/ # Serverless utilities
890
+ ```
891
+
892
+ ### Adding New Components
893
+
894
+ 1. **Create the component**: Follow the established patterns
895
+ 2. **Add tests**: Comprehensive test coverage required
896
+ 3. **Export from index.js**: Make it available to consumers
897
+ 4. **Update documentation**: Keep README current
898
+
899
+ ### Code Style
900
+
901
+ ```bash
902
+ # Format code
903
+ npm run lint:fix
904
+
905
+ # Check linting
906
+ npm run lint
907
+ ```
908
+
909
+ ## API Reference
910
+
911
+ ### Core Exports
912
+
913
+ ```javascript
914
+ const {
915
+ // Integrations
916
+ IntegrationBase,
917
+ IntegrationModel,
918
+ CreateIntegration,
919
+ UpdateIntegration,
920
+ DeleteIntegration,
921
+
922
+ // Modules
923
+ OAuth2Requester,
924
+ ApiKeyRequester,
925
+ Credential,
926
+ Entity,
927
+ // Database
928
+ connectToDatabase,
929
+ mongoose,
930
+ UserModel,
931
+
932
+ // Utilities
933
+ Encrypt,
934
+ Cryptor,
935
+ BaseError,
936
+ debug,
937
+ TimeoutCatcher
938
+ } = require('@friggframework/core');
939
+ ```
940
+
941
+ ### Environment Configuration
942
+
943
+ | Variable | Required | Description |
944
+ |----------|----------|-------------|
945
+ | `MONGO_URI` | Yes | MongoDB connection string |
946
+ | `FRIGG_ENCRYPTION_KEY` | Yes | 256-bit encryption key |
947
+ | `AWS_REGION` | No | AWS region for services |
948
+ | `DEBUG` | No | Debug logging pattern |
949
+ | `LOG_LEVEL` | No | Logging level (debug, info, warn, error) |
77
950
 
78
951
  ## License
79
952
 
80
- This project is licensed under the MIT License. See the LICENSE.md file for details.
953
+ This project is licensed under the MIT License - see the [LICENSE.md](../../LICENSE.md) file for details.
81
954
 
82
955
  ---
83
- Thank you for using Frigg Core! If you have any questions or need further assistance, feel free to reach out to our community on Slack or check out our GitHub issues page.
956
+
957
+ ## Support
958
+
959
+ - 📖 [Documentation](https://docs.friggframework.org)
960
+ - 💬 [Community Slack](https://friggframework.slack.com)
961
+ - 🐛 [Issue Tracker](https://github.com/friggframework/frigg/issues)
962
+ - 📧 [Email Support](mailto:support@friggframework.org)
963
+
964
+ Built with ❤️ by the Frigg Framework team.