@friggframework/core 2.0.0-next.57 → 2.0.0-next.58

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.
@@ -7,22 +7,28 @@ Frigg Commands provide a clean, stable application service layer for all databas
7
7
  ## Why Use Commands?
8
8
 
9
9
  ### 1. **ORM Independence**
10
+
10
11
  Commands isolate your integration code from the underlying database implementation. This allows Frigg to migrate between ORMs (e.g., Mongoose to Prisma) without breaking your integration code.
11
12
 
12
13
  ### 2. **Hexagonal Architecture**
14
+
13
15
  Commands act as the **application service layer** in hexagonal architecture:
14
- - **Domain Layer**: Your use cases and business logic
15
- - **Application Layer**: Frigg Commands (this layer)
16
- - **Infrastructure Layer**: Repositories and database models (hidden from you)
16
+
17
+ - **Domain Layer**: Your use cases and business logic
18
+ - **Application Layer**: Frigg Commands (this layer)
19
+ - **Infrastructure Layer**: Repositories and database models (hidden from you)
17
20
 
18
21
  ### 3. **Single Source of Truth**
22
+
19
23
  All database operations flow through commands, making it easier to:
20
- - Add caching, logging, or monitoring
21
- - Enforce data validation rules
22
- - Maintain consistent error handling
23
- - Track data access patterns
24
+
25
+ - Add caching, logging, or monitoring
26
+ - Enforce data validation rules
27
+ - Maintain consistent error handling
28
+ - Track data access patterns
24
29
 
25
30
  ### 4. **Future-Proof**
31
+
26
32
  When Frigg upgrades its internals, commands maintain backward compatibility. Your integration code continues working without changes.
27
33
 
28
34
  ## Installation
@@ -43,7 +49,7 @@ const MyIntegration = require('./MyIntegration');
43
49
 
44
50
  // Create command set with your integration class
45
51
  const commands = createFriggCommands({
46
- integrationClass: MyIntegration
52
+ integrationClass: MyIntegration,
47
53
  });
48
54
  ```
49
55
 
@@ -54,15 +60,16 @@ class MyIntegration extends IntegrationBase {
54
60
  constructor() {
55
61
  super();
56
62
  this.commands = createFriggCommands({
57
- integrationClass: MyIntegration
63
+ integrationClass: MyIntegration,
58
64
  });
59
65
  }
60
66
 
61
67
  async hydrateFromExternalUser(externalUserId) {
62
68
  // Find integration context by external entity ID
63
- const result = await this.commands.findIntegrationContextByExternalEntityId(
64
- externalUserId
65
- );
69
+ const result =
70
+ await this.commands.findIntegrationContextByExternalEntityId(
71
+ externalUserId
72
+ );
66
73
 
67
74
  if (result.error) {
68
75
  return { error: result.error };
@@ -83,9 +90,11 @@ const { createFriggCommands } = require('@friggframework/core');
83
90
  class AuthenticateUserUseCase {
84
91
  constructor({ commands } = {}) {
85
92
  // Accept injected commands for testing, or create default
86
- this.commands = commands || createFriggCommands({
87
- integrationClass: MyIntegration
88
- });
93
+ this.commands =
94
+ commands ||
95
+ createFriggCommands({
96
+ integrationClass: MyIntegration,
97
+ });
89
98
  }
90
99
 
91
100
  async execute({ appUserId, username, email }) {
@@ -96,7 +105,7 @@ class AuthenticateUserUseCase {
96
105
  user = await this.commands.createUser({
97
106
  appUserId,
98
107
  username,
99
- email
108
+ email,
100
109
  });
101
110
  }
102
111
 
@@ -117,7 +126,7 @@ const user = await commands.createUser({
117
126
  username: 'john@example.com',
118
127
  email: 'john@example.com',
119
128
  appUserId: 'external-user-123',
120
- password: 'optional-password' // For password-based auth
129
+ password: 'optional-password', // For password-based auth
121
130
  });
122
131
 
123
132
  // Find user by app-specific user ID
@@ -127,11 +136,11 @@ const user = await commands.findUserByAppUserId('external-user-123');
127
136
  const user = await commands.findUserByUsername('john@example.com');
128
137
 
129
138
  // Find user by Frigg internal ID
130
- const user = await commands.findUserById('frigg-user-id');
139
+ const user = await commands.findIndividualUserById('frigg-user-id');
131
140
 
132
141
  // Update user
133
142
  const updatedUser = await commands.updateUser('frigg-user-id', {
134
- email: 'newemail@example.com'
143
+ email: 'newemail@example.com',
135
144
  });
136
145
  ```
137
146
 
@@ -148,19 +157,19 @@ const credential = await commands.createCredential({
148
157
  refresh_token: 'refresh_token_value',
149
158
  expires_at: new Date('2024-12-31'),
150
159
  moduleName: 'asana',
151
- authIsValid: true
160
+ authIsValid: true,
152
161
  });
153
162
 
154
163
  // Find credential
155
164
  const credential = await commands.findCredential({
156
165
  userId: 'frigg-user-id',
157
- moduleName: 'asana'
166
+ moduleName: 'asana',
158
167
  });
159
168
 
160
169
  // Update credential (e.g., after token refresh)
161
170
  const updated = await commands.updateCredential('credential-id', {
162
171
  access_token: 'new_access_token',
163
- expires_at: new Date('2025-01-31')
172
+ expires_at: new Date('2025-01-31'),
164
173
  });
165
174
 
166
175
  // Delete credential
@@ -178,14 +187,14 @@ const entity = await commands.createEntity({
178
187
  externalId: 'asana-workspace-123',
179
188
  name: 'My Workspace',
180
189
  moduleName: 'asana',
181
- credentialId: 'credential-id'
190
+ credentialId: 'credential-id',
182
191
  });
183
192
 
184
193
  // Find single entity
185
194
  const entity = await commands.findEntity({
186
195
  userId: 'frigg-user-id',
187
196
  externalId: 'asana-workspace-123',
188
- moduleName: 'asana'
197
+ moduleName: 'asana',
189
198
  });
190
199
 
191
200
  // Find entity by ID
@@ -201,11 +210,14 @@ const asanaEntities = await commands.findEntitiesByUserIdAndModuleName(
201
210
  );
202
211
 
203
212
  // Find multiple entities by IDs
204
- const entities = await commands.findEntitiesByIds(['entity-id-1', 'entity-id-2']);
213
+ const entities = await commands.findEntitiesByIds([
214
+ 'entity-id-1',
215
+ 'entity-id-2',
216
+ ]);
205
217
 
206
218
  // Update entity
207
219
  const updated = await commands.updateEntity('entity-id', {
208
- name: 'Updated Workspace Name'
220
+ name: 'Updated Workspace Name',
209
221
  });
210
222
 
211
223
  // Delete entity
@@ -248,7 +260,7 @@ const commands = createFriggCommands({ integrationClass: MyIntegration });
248
260
  // Test code - inject mocks
249
261
  const mockCommands = {
250
262
  createUser: jest.fn().mockResolvedValue({ id: 'user-123' }),
251
- findUserByAppUserId: jest.fn().mockResolvedValue(null)
263
+ findUserByAppUserId: jest.fn().mockResolvedValue(null),
252
264
  };
253
265
 
254
266
  const useCase = new MyUseCase({ commands: mockCommands });
@@ -261,12 +273,12 @@ const useCase = new MyUseCase({ commands: mockCommands });
261
273
  ```javascript
262
274
  // ❌ Don't do this - commands always use real repositories
263
275
  const commands = createFriggCommands({
264
- userRepository: mockUserRepo // This parameter doesn't exist
276
+ userRepository: mockUserRepo, // This parameter doesn't exist
265
277
  });
266
278
 
267
279
  // ✅ Do this - inject mocked commands into your use cases
268
280
  const useCase = new MyUseCase({
269
- commands: mockCommands
281
+ commands: mockCommands,
270
282
  });
271
283
  ```
272
284
 
@@ -301,6 +313,7 @@ if (result.error) {
301
313
  ### From Direct Model Access
302
314
 
303
315
  **Before (❌ Don't do this):**
316
+
304
317
  ```javascript
305
318
  const { User } = require('@friggframework/core');
306
319
 
@@ -308,6 +321,7 @@ const user = await User.findOne({ appUserId: '123' });
308
321
  ```
309
322
 
310
323
  **After (✅ Do this):**
324
+
311
325
  ```javascript
312
326
  const { createFriggCommands } = require('@friggframework/core');
313
327
 
@@ -318,49 +332,63 @@ const user = await commands.findUserByAppUserId('123');
318
332
  ### From IntegrationRepository (Backend Pattern)
319
333
 
320
334
  **Before (❌ Old pattern):**
335
+
321
336
  ```javascript
322
- const { IntegrationRepository } = require('./repositories/IntegrationRepository');
337
+ const {
338
+ IntegrationRepository,
339
+ } = require('./repositories/IntegrationRepository');
323
340
 
324
341
  this.integrationRepository = new IntegrationRepository(MyIntegration);
325
- const result = await this.integrationRepository.loadIntegrationRecordByAsanaUser(userId);
342
+ const result =
343
+ await this.integrationRepository.loadIntegrationRecordByAsanaUser(userId);
326
344
  ```
327
345
 
328
346
  **After (✅ New pattern):**
347
+
329
348
  ```javascript
330
349
  const { createFriggCommands } = require('@friggframework/core');
331
350
 
332
351
  this.commands = createFriggCommands({ integrationClass: MyIntegration });
333
- const result = await this.commands.findIntegrationContextByExternalEntityId(userId);
352
+ const result = await this.commands.findIntegrationContextByExternalEntityId(
353
+ userId
354
+ );
334
355
  ```
335
356
 
336
357
  ## Best Practices
337
358
 
338
359
  ### 1. Create Commands Once
360
+
339
361
  Initialize commands in your constructor:
340
362
 
341
363
  ```javascript
342
364
  class MyIntegration extends IntegrationBase {
343
365
  constructor() {
344
366
  super();
345
- this.commands = createFriggCommands({ integrationClass: MyIntegration });
367
+ this.commands = createFriggCommands({
368
+ integrationClass: MyIntegration,
369
+ });
346
370
  }
347
371
  }
348
372
  ```
349
373
 
350
374
  ### 2. Pass Commands to Use Cases
375
+
351
376
  Use dependency injection for testability:
352
377
 
353
378
  ```javascript
354
379
  class MyUseCase {
355
380
  constructor({ commands } = {}) {
356
- this.commands = commands || createFriggCommands({
357
- integrationClass: MyIntegration
358
- });
381
+ this.commands =
382
+ commands ||
383
+ createFriggCommands({
384
+ integrationClass: MyIntegration,
385
+ });
359
386
  }
360
387
  }
361
388
  ```
362
389
 
363
390
  ### 3. Use Specific Finders
391
+
364
392
  Use the most specific finder method:
365
393
 
366
394
  ```javascript
@@ -372,6 +400,7 @@ const user = await commands.findUser({ appUserId: '123' });
372
400
  ```
373
401
 
374
402
  ### 4. Handle Null Returns
403
+
375
404
  Most finders return `null` if not found:
376
405
 
377
406
  ```javascript
@@ -385,37 +414,38 @@ if (!user) {
385
414
 
386
415
  ## Command Reference
387
416
 
388
- | Category | Command | Description |
389
- |----------|---------|-------------|
390
- | **User** | `createUser(data)` | Create new Frigg user |
391
- | | `findUserByAppUserId(appUserId)` | Find by external app user ID |
392
- | | `findUserByUsername(username)` | Find by username |
393
- | | `findUserById(id)` | Find by Frigg user ID |
394
- | | `updateUser(id, updates)` | Update user properties |
395
- | **Credential** | `createCredential(data)` | Create OAuth credential |
396
- | | `findCredential(filter)` | Find credential by filter |
397
- | | `updateCredential(id, updates)` | Update credential (token refresh) |
398
- | | `deleteCredential(id)` | Delete credential |
399
- | **Entity** | `createEntity(data)` | Create module entity |
400
- | | `findEntity(filter)` | Find entity by filter |
401
- | | `findEntityById(id)` | Find by entity ID |
402
- | | `findEntitiesByUserId(userId)` | Find all user entities |
403
- | | `findEntitiesByUserIdAndModuleName(userId, moduleName)` | Find user entities for module |
404
- | | `findEntitiesByIds(ids)` | Find multiple by IDs |
405
- | | `updateEntity(id, updates)` | Update entity properties |
406
- | | `deleteEntity(id)` | Delete entity |
407
- | **Integration** | `findIntegrationContextByExternalEntityId(externalId)` | Load integration + modules by external ID |
408
- | | `loadIntegrationContextById(integrationId)` | Load integration + modules by ID |
417
+ | Category | Command | Description |
418
+ | --------------- | ------------------------------------------------------- | ----------------------------------------- |
419
+ | **User** | `createUser(data)` | Create new Frigg user |
420
+ | | `findUserByAppUserId(appUserId)` | Find by external app user ID |
421
+ | | `findUserByUsername(username)` | Find by username |
422
+ | | `findIndividualUserById(id)` | Find by Frigg user ID |
423
+ | | `updateUser(id, updates)` | Update user properties |
424
+ | **Credential** | `createCredential(data)` | Create OAuth credential |
425
+ | | `findCredential(filter)` | Find credential by filter |
426
+ | | `updateCredential(id, updates)` | Update credential (token refresh) |
427
+ | | `deleteCredential(id)` | Delete credential |
428
+ | **Entity** | `createEntity(data)` | Create module entity |
429
+ | | `findEntity(filter)` | Find entity by filter |
430
+ | | `findEntityById(id)` | Find by entity ID |
431
+ | | `findEntitiesByUserId(userId)` | Find all user entities |
432
+ | | `findEntitiesByUserIdAndModuleName(userId, moduleName)` | Find user entities for module |
433
+ | | `findEntitiesByIds(ids)` | Find multiple by IDs |
434
+ | | `updateEntity(id, updates)` | Update entity properties |
435
+ | | `deleteEntity(id)` | Delete entity |
436
+ | **Integration** | `findIntegrationContextByExternalEntityId(externalId)` | Load integration + modules by external ID |
437
+ | | `loadIntegrationContextById(integrationId)` | Load integration + modules by ID |
409
438
 
410
439
  ## Support
411
440
 
412
441
  For questions or issues with commands:
442
+
413
443
  1. Check this README
414
444
  2. Review the main Frigg documentation
415
445
  3. Open an issue on the Frigg Framework repository
416
446
 
417
447
  ## Related Documentation
418
448
 
419
- - [Frigg Framework Overview](../../README.md)
420
- - [Integration Development Guide](../../docs/integration-guide.md)
421
- - [Hexagonal Architecture](../../docs/architecture.md)
449
+ - [Frigg Framework Overview](../../README.md)
450
+ - [Integration Development Guide](../../docs/integration-guide.md)
451
+ - [Hexagonal Architecture](../../docs/architecture.md)
@@ -138,11 +138,11 @@ function createUserCommands() {
138
138
  },
139
139
 
140
140
  /**
141
- * Find a user by their ID
142
- * @param {string} userId - User ID to search for
143
- * @returns {Promise<Object|null>} User object or null if not found
141
+ * Find an individual user by their ID
142
+ * @param {string} userId - Individual user ID to search for
143
+ * @returns {Promise<Object|null>} Individual user object or null if not found
144
144
  */
145
- async findUserById(userId) {
145
+ async findIndividualUserById(userId) {
146
146
  try {
147
147
  if (!userId) {
148
148
  const error = new Error('userId is required');
@@ -159,7 +159,7 @@ function createUserCommands() {
159
159
  }
160
160
 
161
161
  return {
162
- id: user._id.toString(),
162
+ id: user._id?.toString() || user.id,
163
163
  username: user.username,
164
164
  email: user.email,
165
165
  appUserId: user.appUserId,
@@ -169,6 +169,37 @@ function createUserCommands() {
169
169
  }
170
170
  },
171
171
 
172
+ /**
173
+ * Find an organization user by their ID
174
+ * @param {string} userId - Organization user ID to search for
175
+ * @returns {Promise<Object|null>} Organization user object or null if not found
176
+ */
177
+ async findOrganizationUserById(userId) {
178
+ try {
179
+ if (!userId) {
180
+ const error = new Error('userId is required');
181
+ error.code = 'INVALID_USER_DATA';
182
+ throw error;
183
+ }
184
+
185
+ const user = await userRepository.findOrganizationUserById(
186
+ userId
187
+ );
188
+
189
+ if (!user) {
190
+ return null;
191
+ }
192
+
193
+ return {
194
+ id: user.id,
195
+ appOrgId: user.appOrgId,
196
+ name: user.name,
197
+ };
198
+ } catch (error) {
199
+ return mapErrorToResponse(error);
200
+ }
201
+ },
202
+
172
203
  /**
173
204
  * Update a user by ID
174
205
  * @param {string} userId - User ID to update