@friggframework/core 2.0.0-next.41 → 2.0.0-next.43

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 (197) 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 +27 -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 +122 -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 +318 -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/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
  134. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  135. package/prisma-postgresql/schema.prisma +300 -0
  136. package/syncs/manager.js +468 -443
  137. package/syncs/repositories/sync-repository-factory.js +38 -0
  138. package/syncs/repositories/sync-repository-interface.js +109 -0
  139. package/syncs/repositories/sync-repository-mongo.js +239 -0
  140. package/syncs/repositories/sync-repository-postgres.js +319 -0
  141. package/syncs/sync.js +0 -1
  142. package/token/repositories/token-repository-factory.js +33 -0
  143. package/token/repositories/token-repository-interface.js +131 -0
  144. package/token/repositories/token-repository-mongo.js +212 -0
  145. package/token/repositories/token-repository-postgres.js +257 -0
  146. package/token/repositories/token-repository.js +219 -0
  147. package/types/integrations/index.d.ts +2 -6
  148. package/types/module-plugin/index.d.ts +5 -57
  149. package/types/syncs/index.d.ts +0 -2
  150. package/user/repositories/user-repository-factory.js +46 -0
  151. package/user/repositories/user-repository-interface.js +198 -0
  152. package/user/repositories/user-repository-mongo.js +250 -0
  153. package/user/repositories/user-repository-postgres.js +311 -0
  154. package/user/tests/doubles/test-user-repository.js +72 -0
  155. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  156. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  157. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  158. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  159. package/user/tests/use-cases/login-user.test.js +140 -0
  160. package/user/use-cases/create-individual-user.js +61 -0
  161. package/user/use-cases/create-organization-user.js +47 -0
  162. package/user/use-cases/create-token-for-user-id.js +30 -0
  163. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  164. package/user/use-cases/login-user.js +122 -0
  165. package/user/user.js +77 -0
  166. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  167. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  168. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  169. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  170. package/websocket/repositories/websocket-connection-repository.js +160 -0
  171. package/database/models/State.js +0 -9
  172. package/database/models/Token.js +0 -70
  173. package/database/mongo.js +0 -171
  174. package/encrypt/Cryptor.test.js +0 -32
  175. package/encrypt/encrypt.js +0 -104
  176. package/encrypt/encrypt.test.js +0 -1069
  177. package/handlers/routers/middleware/loadUser.js +0 -15
  178. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  179. package/integrations/create-frigg-backend.js +0 -31
  180. package/integrations/integration-factory.js +0 -251
  181. package/integrations/integration-mapping.js +0 -43
  182. package/integrations/integration-model.js +0 -46
  183. package/integrations/integration-user.js +0 -144
  184. package/integrations/test/integration-base.test.js +0 -144
  185. package/module-plugin/auther.js +0 -393
  186. package/module-plugin/credential.js +0 -22
  187. package/module-plugin/entity-manager.js +0 -70
  188. package/module-plugin/manager.js +0 -169
  189. package/module-plugin/module-factory.js +0 -61
  190. package/module-plugin/test/auther.test.js +0 -97
  191. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  192. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  193. /package/{module-plugin → modules}/requester/basic.js +0 -0
  194. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.js +0 -0
  196. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  197. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
package/core/CLAUDE.md ADDED
@@ -0,0 +1,690 @@
1
+ # CLAUDE.md - Frigg Core Runtime System
2
+
3
+ This file provides guidance to Claude Code when working with the Frigg Framework's core runtime system in `packages/core/core/`.
4
+
5
+ ## Critical Context (Read First)
6
+
7
+ - **Package Purpose**: Core runtime system and foundational classes for Frigg Lambda execution
8
+ - **Main Components**: Handler factory, Worker base class, Delegate pattern, Module loading
9
+ - **Core Architecture**: Lambda-optimized runtime with connection pooling, error handling, secrets management
10
+ - **Key Integration**: AWS Lambda, SQS job processing, MongoDB connections, AWS Secrets Manager
11
+ - **Security Model**: Automatic secrets injection, database connection management, user-facing error sanitization
12
+ - **DO NOT**: Expose internal errors to users, bypass connection pooling, skip database initialization
13
+
14
+ ## Core Components Architecture
15
+
16
+ ### Handler Creation System (`create-handler.js:9-67`)
17
+
18
+ **Purpose**: Factory for creating Lambda handlers with consistent infrastructure setup
19
+
20
+ **Key Features**:
21
+ - **Database Connection Management**: Automatic MongoDB connection with pooling
22
+ - **Secrets Management**: AWS Secrets Manager integration via `SECRET_ARN` env var
23
+ - **Error Sanitization**: Prevents internal details from leaking to end users
24
+ - **Debug Logging**: Request/response logging with structured debug info
25
+ - **Connection Optimization**: `context.callbackWaitsForEmptyEventLoop = false` for reuse
26
+
27
+ **Handler Configuration Options**:
28
+ ```javascript
29
+ const handler = createHandler({
30
+ eventName: 'MyIntegration', // For logging/debugging
31
+ isUserFacingResponse: true, // true = sanitize errors, false = pass through
32
+ method: async (event, context) => {}, // Your Lambda function logic
33
+ shouldUseDatabase: true // false = skip MongoDB connection
34
+ });
35
+ ```
36
+
37
+ **Error Handling Patterns**:
38
+ - **User-Facing**: Returns 500 with generic "Internal Error Occurred" message
39
+ - **Server-to-Server**: Re-throws errors for AWS to handle
40
+ - **Halt Errors**: `error.isHaltError = true` logs but returns success (no retry)
41
+
42
+ ### Worker Base Class (`Worker.js:9-83`)
43
+
44
+ **Purpose**: Base class for SQS job processing with standardized patterns
45
+
46
+ **Core Responsibilities**:
47
+ - **Queue Management**: Get SQS queue URLs and send messages
48
+ - **Batch Processing**: Process multiple SQS records in sequence
49
+ - **Message Validation**: Extensible parameter validation system
50
+ - **Error Handling**: Structured error handling for async job processing
51
+
52
+ **Usage Pattern**:
53
+ ```javascript
54
+ class MyWorker extends Worker {
55
+ async _run(params, context = {}) {
56
+ // Your job processing logic here
57
+ // params are already JSON.parsed from SQS message body
58
+ }
59
+
60
+ _validateParams(params) {
61
+ // Validate required parameters
62
+ this._verifyParamExists(params, 'requiredField');
63
+ }
64
+ }
65
+
66
+ // In your Lambda handler
67
+ const worker = new MyWorker();
68
+ await worker.run(event, context); // Process SQS Records
69
+ ```
70
+
71
+ **Message Sending**:
72
+ ```javascript
73
+ await worker.send({
74
+ QueueUrl: 'https://sqs.region.amazonaws.com/account/queue',
75
+ jobType: 'processAttachment',
76
+ integrationId: 'abc123',
77
+ // ... other job parameters
78
+ }, delaySeconds);
79
+ ```
80
+
81
+ ### Delegate Pattern System (`Delegate.js:3-27`)
82
+
83
+ **Purpose**: Observer/delegation pattern for decoupled component communication
84
+
85
+ **Core Concepts**:
86
+ - **Notification System**: Components notify delegates of events/state changes
87
+ - **Type Safety**: `delegateTypes` array defines valid notification strings
88
+ - **Bidirectional**: Supports both sending and receiving notifications
89
+ - **Null Safety**: Gracefully handles missing delegates
90
+
91
+ **Implementation Pattern**:
92
+ ```javascript
93
+ class MyIntegration extends Delegate {
94
+ constructor(params) {
95
+ super(params);
96
+ this.delegateTypes = ['processComplete', 'errorOccurred', 'statusUpdate'];
97
+ }
98
+
99
+ async processData(data) {
100
+ // Do work
101
+ await this.notify('statusUpdate', { progress: 50 });
102
+ // More work
103
+ await this.notify('processComplete', { result: data });
104
+ }
105
+
106
+ async receiveNotification(notifier, delegateString, object) {
107
+ // Handle notifications from other components
108
+ switch(delegateString) {
109
+ case 'dataReady':
110
+ await this.processData(object);
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Module Loading System (`load-installed-modules.js:1-1085`)
118
+
119
+ **Purpose**: Dynamic loading and registration of integration modules
120
+
121
+ **Key Features**:
122
+ - **Package Discovery**: Automatically find `@friggframework/api-module-*` packages
123
+ - **Module Registration**: Load and register integration classes
124
+ - **Configuration Management**: Handle module-specific configuration
125
+ - **Dependency Resolution**: Manage inter-module dependencies
126
+
127
+ ## Runtime Lifecycle & Patterns
128
+
129
+ ### Lambda Handler Lifecycle
130
+ 1. **Pre-Execution Setup**:
131
+ ```javascript
132
+ initDebugLog(eventName, event); // Debug logging setup
133
+ await secretsToEnv(); // Secrets Manager injection
134
+ context.callbackWaitsForEmptyEventLoop = false; // Connection pooling
135
+ ```
136
+
137
+ 2. **Database Connection**:
138
+ ```javascript
139
+ if (shouldUseDatabase) {
140
+ await connectToDatabase(); // MongoDB connection with pooling
141
+ }
142
+ ```
143
+
144
+ 3. **Method Execution**:
145
+ ```javascript
146
+ return await method(event, context); // Your integration logic
147
+ ```
148
+
149
+ 4. **Error Handling & Cleanup**:
150
+ ```javascript
151
+ flushDebugLog(error); // Debug info flush on error
152
+ // Sanitized error response for user-facing endpoints
153
+ ```
154
+
155
+ ### SQS Job Processing Lifecycle
156
+ 1. **Batch Processing**: Process all records in `event.Records` sequentially
157
+ 2. **Message Parsing**: JSON.parse message body for parameters
158
+ 3. **Validation**: Run custom validation on parsed parameters
159
+ 4. **Execution**: Call `_run()` method with validated parameters
160
+ 5. **Error Propagation**: Let AWS handle retries/DLQ for failed jobs
161
+
162
+ ### Secrets Management Integration
163
+ - **Automatic Injection**: If `SECRET_ARN` environment variable is set
164
+ - **Environment Variables**: Secrets automatically set as `process.env` variables
165
+ - **Security**: No secrets logging or exposure in error messages
166
+ - **Caching**: Secrets cached for Lambda container lifetime
167
+
168
+ ## Database Connection Patterns
169
+
170
+ ### Connection Pooling Strategy
171
+ ```javascript
172
+ // Mongoose connection reuse across Lambda invocations
173
+ context.callbackWaitsForEmptyEventLoop = false;
174
+ await connectToDatabase(); // Reuses existing connection if available
175
+ ```
176
+
177
+ ### Database Usage Patterns
178
+ ```javascript
179
+ // Conditional database connection
180
+ const handler = createHandler({
181
+ shouldUseDatabase: false, // Skip for database-free operations
182
+ method: async (event) => {
183
+ // No DB operations needed
184
+ return { statusCode: 200, body: 'OK' };
185
+ }
186
+ });
187
+ ```
188
+
189
+ ## Error Handling Architecture
190
+
191
+ ### Error Classification
192
+ 1. **User-Facing Errors**: `isUserFacingResponse: true`
193
+ - Returns generic 500 error message
194
+ - Prevents information disclosure
195
+ - Logs full error details internally
196
+
197
+ 2. **Server-to-Server Errors**: `isUserFacingResponse: false`
198
+ - Re-throws original error for AWS handling
199
+ - Used for SQS, SNS, and internal API calls
200
+ - Enables proper retry mechanisms
201
+
202
+ 3. **Halt Errors**: `error.isHaltError = true`
203
+ - Logs error but returns success
204
+ - Prevents infinite retries for known issues
205
+ - Used for graceful degradation scenarios
206
+
207
+ ### Debug Logging Strategy
208
+ ```javascript
209
+ initDebugLog(eventName, event); // Start logging context
210
+ // ... your code ...
211
+ flushDebugLog(error); // Flush on error (includes full context)
212
+ ```
213
+
214
+ ## Integration Development Patterns
215
+
216
+ ### Extending Worker for Job Processing
217
+ ```javascript
218
+ class AttachmentWorker extends Worker {
219
+ _validateParams(params) {
220
+ this._verifyParamExists(params, 'integrationId');
221
+ this._verifyParamExists(params, 'attachmentUrl');
222
+ this._verifyParamExists(params, 'destination');
223
+ }
224
+
225
+ async _run(params, context) {
226
+ const { integrationId, attachmentUrl, destination } = params;
227
+ // Process attachment upload/download
228
+ // Handle errors gracefully
229
+ // Update job status
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### Creating Custom Handlers
235
+ ```javascript
236
+ const myIntegrationHandler = createHandler({
237
+ eventName: 'MyIntegration',
238
+ isUserFacingResponse: true, // Sanitize errors for users
239
+ shouldUseDatabase: true, // Need database access
240
+ method: async (event, context) => {
241
+ // Your integration logic here
242
+ // Database is already connected
243
+ // Secrets are in process.env
244
+
245
+ return {
246
+ statusCode: 200,
247
+ body: JSON.stringify({ success: true })
248
+ };
249
+ }
250
+ });
251
+ ```
252
+
253
+ ### Delegate Pattern for Integration Communication
254
+ ```javascript
255
+ class IntegrationManager extends Delegate {
256
+ constructor() {
257
+ super();
258
+ this.delegateTypes = [
259
+ 'authenticationComplete',
260
+ 'syncStarted',
261
+ 'syncComplete',
262
+ 'errorOccurred'
263
+ ];
264
+ }
265
+
266
+ async startSync(integrationId) {
267
+ await this.notify('syncStarted', { integrationId });
268
+ // ... sync logic ...
269
+ await this.notify('syncComplete', { integrationId, recordCount: 100 });
270
+ }
271
+ }
272
+ ```
273
+
274
+ ## Performance Optimization Patterns
275
+
276
+ ### Connection Reuse
277
+ ```javascript
278
+ // ALWAYS set this in handlers for performance
279
+ context.callbackWaitsForEmptyEventLoop = false;
280
+ ```
281
+
282
+ ### Conditional Database Usage
283
+ ```javascript
284
+ // Skip database for lightweight operations
285
+ const handler = createHandler({
286
+ shouldUseDatabase: false, // Faster cold starts
287
+ method: healthCheckMethod
288
+ });
289
+ ```
290
+
291
+ ### SQS Batch Processing Optimization
292
+ ```javascript
293
+ // Process records sequentially (not parallel) for resource control
294
+ for (const record of records) {
295
+ await this._run(JSON.parse(record.body), context);
296
+ }
297
+ ```
298
+
299
+ ## Repository & Use Case Architecture
300
+
301
+ The Frigg Framework follows DDD/Hexagonal Architecture with clear separation between handlers, use cases, and repositories.
302
+
303
+ ### Repository Pattern in Core
304
+
305
+ **Purpose**: Abstract database and external system access into dedicated repository classes.
306
+
307
+ **Structure**:
308
+ ```javascript
309
+ // Example: packages/core/database/websocket-connection-repository.js
310
+ class WebsocketConnectionRepository {
311
+ /**
312
+ * Create a new WebSocket connection record
313
+ * Pure database operation - no business logic
314
+ */
315
+ async createConnection(connectionId) {
316
+ return await WebsocketConnection.create({ connectionId });
317
+ }
318
+
319
+ /**
320
+ * Delete a WebSocket connection record
321
+ * Returns raw deletion result
322
+ */
323
+ async deleteConnection(connectionId) {
324
+ return await WebsocketConnection.deleteOne({ connectionId });
325
+ }
326
+
327
+ /**
328
+ * Get all active connections
329
+ * Returns raw data from database
330
+ */
331
+ async getActiveConnections() {
332
+ return await WebsocketConnection.getActiveConnections();
333
+ }
334
+ }
335
+ ```
336
+
337
+ **Repository Responsibilities**:
338
+ - ✅ **CRUD operations** - Create, Read, Update, Delete database records
339
+ - ✅ **Query execution** - Run database queries and return results
340
+ - ✅ **Data access only** - No interpretation or decision-making
341
+ - ✅ **Atomic operations** - Each method performs one database operation
342
+ - ❌ **NO business logic** - Don't decide what data means or what to do with it
343
+ - ❌ **NO orchestration** - Don't coordinate multiple operations
344
+
345
+ **Real Repository Examples**:
346
+ - `WebsocketConnectionRepository` - WebSocket persistence (packages/core/database/websocket-connection-repository.js)
347
+ - `SyncRepository` - Sync object management (packages/core/syncs/sync-repository.js)
348
+ - `IntegrationMappingRepository` - Integration mappings (packages/core/integrations/integration-mapping-repository.js)
349
+ - `TokenRepository` - Token operations (packages/core/database/token-repository.js)
350
+ - `HealthCheckRepository` - Health check data access (packages/core/database/health-check-repository.js)
351
+
352
+ ### Use Case Pattern in Core
353
+
354
+ **Purpose**: Contain business logic, orchestration, and workflow coordination.
355
+
356
+ **Structure**:
357
+ ```javascript
358
+ // Example: packages/core/database/use-cases/check-database-health-use-case.js
359
+ class CheckDatabaseHealthUseCase {
360
+ constructor({ healthCheckRepository }) {
361
+ // Dependency injection - receive repository via constructor
362
+ this.repository = healthCheckRepository;
363
+ }
364
+
365
+ async execute() {
366
+ // 1. Get raw data from repository
367
+ const { stateName, isConnected } = this.repository.getDatabaseConnectionState();
368
+
369
+ // 2. Apply business logic - determine health status
370
+ const result = {
371
+ status: isConnected ? 'healthy' : 'unhealthy',
372
+ state: stateName,
373
+ };
374
+
375
+ // 3. Orchestration - conditionally perform additional checks
376
+ if (isConnected) {
377
+ result.responseTime = await this.repository.pingDatabase(2000);
378
+ }
379
+
380
+ return result;
381
+ }
382
+ }
383
+ ```
384
+
385
+ **Use Case Responsibilities**:
386
+ - ✅ **Business logic** - Make decisions based on data
387
+ - ✅ **Orchestration** - Coordinate multiple repository calls
388
+ - ✅ **Validation** - Enforce business rules
389
+ - ✅ **Workflow** - Determine what happens next
390
+ - ✅ **Error handling** - Handle domain-specific errors
391
+ - ❌ **NO direct database access** - Always use repositories
392
+ - ❌ **NO HTTP concerns** - Don't know about status codes or headers
393
+
394
+ **Real Use Case Examples**:
395
+ - `CheckDatabaseHealthUseCase` - Database health business logic (packages/core/database/use-cases/check-database-health-use-case.js)
396
+ - `TestEncryptionUseCase` - Encryption testing workflow (packages/core/database/use-cases/test-encryption-use-case.js)
397
+
398
+ ### Handler Pattern in Core
399
+
400
+ **Purpose**: Translate Lambda/HTTP/SQS events into use case calls.
401
+
402
+ **Handler Should ONLY**:
403
+ - Define routes and event handlers
404
+ - Call use cases (NOT repositories)
405
+ - Map use case results to HTTP/Lambda responses
406
+ - Handle protocol-specific concerns (status codes, headers)
407
+
408
+ **❌ WRONG - Handler contains business logic**:
409
+ ```javascript
410
+ // BAD: Business logic in handler
411
+ router.get('/health', async (req, res) => {
412
+ const state = mongoose.connection.readyState;
413
+ const isHealthy = state === 1; // ❌ Business logic in handler
414
+
415
+ if (isHealthy) { // ❌ Orchestration in handler
416
+ const pingStart = Date.now();
417
+ await mongoose.connection.db.admin().ping(); // ❌ Direct DB access
418
+ const responseTime = Date.now() - pingStart;
419
+ res.json({ status: 'healthy', responseTime });
420
+ }
421
+ });
422
+ ```
423
+
424
+ **✅ CORRECT - Handler delegates to use case**:
425
+ ```javascript
426
+ // GOOD: Handler calls use case
427
+ const healthCheckRepository = new HealthCheckRepository();
428
+ const checkDatabaseHealthUseCase = new CheckDatabaseHealthUseCase({
429
+ healthCheckRepository
430
+ });
431
+
432
+ router.get('/health', async (req, res) => {
433
+ // Call use case - all business logic is there
434
+ const health = await checkDatabaseHealthUseCase.execute();
435
+
436
+ // Handler only maps to HTTP response
437
+ const statusCode = health.status === 'healthy' ? 200 : 503;
438
+ res.status(statusCode).json(health);
439
+ });
440
+ ```
441
+
442
+ ### Dependency Direction
443
+
444
+ **The Golden Rule**:
445
+ > "Handlers ONLY call Use Cases, NEVER Repositories or Business Logic directly"
446
+
447
+ **Correct Flow**:
448
+ ```
449
+ Handler/Router (createHandler)
450
+ ↓ calls
451
+ Use Case (execute)
452
+ ↓ calls
453
+ Repository (CRUD methods)
454
+ ↓ accesses
455
+ Database/External System
456
+ ```
457
+
458
+ **Why This Matters**:
459
+ - **Testability**: Use cases can be tested with mocked repositories
460
+ - **Reusability**: Use cases can be called from handlers, CLI, background jobs
461
+ - **Maintainability**: Business logic is centralized, not scattered across handlers
462
+ - **Flexibility**: Swap repository implementations without changing use cases
463
+
464
+ ### Migration from Old Patterns
465
+
466
+ **Old Pattern (Mongoose models everywhere)**:
467
+ ```javascript
468
+ // BAD: Direct model access in handlers
469
+ const handler = createHandler({
470
+ method: async (event) => {
471
+ const user = await User.findById(event.userId); // ❌ Direct model access
472
+ if (!user.isActive) { // ❌ Business logic in handler
473
+ throw new Error('User not active');
474
+ }
475
+ await Sync.create({ userId: user.id }); // ❌ Direct model access
476
+ }
477
+ });
478
+ ```
479
+
480
+ **New Pattern (Repository + Use Case)**:
481
+ ```javascript
482
+ // GOOD: Repository abstracts data access
483
+ class UserRepository {
484
+ async findById(userId) {
485
+ return await User.findById(userId);
486
+ }
487
+ }
488
+
489
+ class SyncRepository {
490
+ async createSync(data) {
491
+ return await Sync.create(data);
492
+ }
493
+ }
494
+
495
+ // GOOD: Use case contains business logic
496
+ class ActivateUserSyncUseCase {
497
+ constructor({ userRepository, syncRepository }) {
498
+ this.userRepo = userRepository;
499
+ this.syncRepo = syncRepository;
500
+ }
501
+
502
+ async execute(userId) {
503
+ const user = await this.userRepo.findById(userId);
504
+
505
+ if (!user.isActive) { // ✅ Business logic in use case
506
+ throw new Error('User not active');
507
+ }
508
+
509
+ return await this.syncRepo.createSync({ userId: user.id });
510
+ }
511
+ }
512
+
513
+ // GOOD: Handler delegates to use case
514
+ const handler = createHandler({
515
+ method: async (event) => {
516
+ const useCase = new ActivateUserSyncUseCase({
517
+ userRepository: new UserRepository(),
518
+ syncRepository: new SyncRepository()
519
+ });
520
+ return await useCase.execute(event.userId);
521
+ }
522
+ });
523
+ ```
524
+
525
+ ### Integration with Worker Pattern
526
+
527
+ **Workers should also follow this pattern**:
528
+
529
+ ```javascript
530
+ class ProcessAttachmentWorker extends Worker {
531
+ constructor() {
532
+ super();
533
+ // Inject repositories into use case
534
+ this.useCase = new ProcessAttachmentUseCase({
535
+ asanaRepository: new AsanaRepository(),
536
+ frontifyRepository: new FrontifyRepository()
537
+ });
538
+ }
539
+
540
+ _validateParams(params) {
541
+ this._verifyParamExists(params, 'attachmentId');
542
+ }
543
+
544
+ async _run(params, context) {
545
+ // Worker delegates to use case
546
+ return await this.useCase.execute(params.attachmentId);
547
+ }
548
+ }
549
+ ```
550
+
551
+ ### When to Extract to Repository/Use Case
552
+
553
+ **Extract to Repository when you see**:
554
+ - Direct Mongoose model calls (`User.findById()`, `Sync.create()`)
555
+ - Database queries in handlers or business logic
556
+ - External API calls scattered across codebase
557
+ - File system or AWS SDK operations in handlers
558
+
559
+ **Extract to Use Case when you see**:
560
+ - Business logic in handlers (if/else based on data)
561
+ - Orchestration of multiple operations
562
+ - Validation and error handling logic
563
+ - Workflow coordination
564
+
565
+ ### Testing with Repository/Use Case Pattern
566
+
567
+ **Repository Tests** (Integration tests with real DB):
568
+ ```javascript
569
+ describe('WebsocketConnectionRepository', () => {
570
+ it('creates connection record', async () => {
571
+ const repo = new WebsocketConnectionRepository();
572
+ const result = await repo.createConnection('conn-123');
573
+ expect(result.connectionId).toBe('conn-123');
574
+ });
575
+ });
576
+ ```
577
+
578
+ **Use Case Tests** (Unit tests with mocked repositories):
579
+ ```javascript
580
+ describe('CheckDatabaseHealthUseCase', () => {
581
+ it('returns unhealthy when disconnected', async () => {
582
+ const mockRepo = {
583
+ getDatabaseConnectionState: () => ({
584
+ stateName: 'disconnected',
585
+ isConnected: false
586
+ })
587
+ };
588
+ const useCase = new CheckDatabaseHealthUseCase({
589
+ healthCheckRepository: mockRepo
590
+ });
591
+ const result = await useCase.execute();
592
+ expect(result.status).toBe('unhealthy');
593
+ });
594
+ });
595
+ ```
596
+
597
+ **Handler Tests** (HTTP/Lambda response tests):
598
+ ```javascript
599
+ describe('Health Handler', () => {
600
+ it('returns 503 when unhealthy', async () => {
601
+ // Mock use case
602
+ const mockUseCase = {
603
+ execute: async () => ({ status: 'unhealthy' })
604
+ };
605
+ // Test HTTP response
606
+ const response = await handler(mockEvent, mockContext);
607
+ expect(response.statusCode).toBe(503);
608
+ });
609
+ });
610
+ ```
611
+
612
+ ## Anti-Patterns to Avoid
613
+
614
+ ### Core Runtime Anti-Patterns
615
+ ❌ **Don't expose internal errors** to user-facing endpoints - use `isUserFacingResponse: true`
616
+ ❌ **Don't skip connection optimization** - always set `callbackWaitsForEmptyEventLoop = false`
617
+ ❌ **Don't parallel process SQS records** - sequential processing prevents resource exhaustion
618
+ ❌ **Don't hardcode queue URLs** - use the Worker's `getQueueURL()` method
619
+ ❌ **Don't bypass parameter validation** - always implement `_validateParams()` in Workers
620
+ ❌ **Don't leak secrets in logs** - the system handles this, don't override
621
+ ❌ **Don't ignore delegate types** - define valid `delegateTypes` array for type safety
622
+
623
+ ### DDD/Hexagonal Architecture Anti-Patterns
624
+ ❌ **Don't access models directly in handlers** - create repositories to abstract data access
625
+ ❌ **Don't put business logic in handlers** - extract to use cases
626
+ ❌ **Don't call repositories from handlers** - always go through use cases
627
+ ❌ **Don't put orchestration in repositories** - repositories should be atomic CRUD operations
628
+ ❌ **Don't skip dependency injection** - inject repositories into use cases via constructor
629
+ ❌ **Don't create "god" use cases** - keep use cases focused on single business operations
630
+ ❌ **Don't mix database queries with business logic** - separate into repository + use case
631
+
632
+ ## Testing Patterns
633
+
634
+ ### Handler Testing
635
+ ```javascript
636
+ const { createHandler } = require('@friggframework/core/core');
637
+
638
+ const testHandler = createHandler({
639
+ isUserFacingResponse: false, // Get full errors in tests
640
+ shouldUseDatabase: false, // Mock/skip DB in tests
641
+ method: yourTestMethod
642
+ });
643
+
644
+ // Test with mock event/context
645
+ const result = await testHandler(mockEvent, mockContext);
646
+ ```
647
+
648
+ ### Worker Testing
649
+ ```javascript
650
+ class TestWorker extends Worker {
651
+ _validateParams(params) {
652
+ this._verifyParamExists(params, 'testField');
653
+ }
654
+
655
+ async _run(params, context) {
656
+ // Your test logic
657
+ return { processed: true };
658
+ }
659
+ }
660
+
661
+ // Test SQS record processing
662
+ const worker = new TestWorker();
663
+ await worker.run({
664
+ Records: [{
665
+ body: JSON.stringify({ testField: 'value' })
666
+ }]
667
+ });
668
+ ```
669
+
670
+ ## Environment Variables
671
+
672
+ ### Required Variables
673
+ - `AWS_REGION`: AWS region for SQS operations
674
+ - `SECRET_ARN`: (Optional) AWS Secrets Manager secret ARN for automatic injection
675
+
676
+ ### Database Variables
677
+ - MongoDB connection variables (handled by `../database/mongo`)
678
+ - See database module documentation for complete list
679
+
680
+ ### Queue Variables
681
+ - Queue URLs typically passed as parameters, not environment variables
682
+ - Use Worker's `getQueueURL()` method for dynamic queue discovery
683
+
684
+ ## Security Considerations
685
+
686
+ - **Secrets**: Never log or expose secrets in error messages
687
+ - **Error Messages**: Always sanitize errors for user-facing responses
688
+ - **Database**: Connection pooling reuses connections securely
689
+ - **SQS**: Message validation prevents injection attacks
690
+ - **Logging**: Debug logs include sensitive data - handle carefully in production