@friggframework/core 2.0.0-next.70 → 2.0.0-next.72

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.
@@ -6,10 +6,7 @@ const {
6
6
  getArrayParamAndVerifyParamType,
7
7
  getAndVerifyType,
8
8
  } = require('./get');
9
- const { expectShallowEqualDbObject } = require('./is-equal');
10
-
11
9
  module.exports = {
12
- expectShallowEqualDbObject,
13
10
  get,
14
11
  getAll,
15
12
  verifyType,
@@ -29,7 +29,7 @@ const createHandler = (optionByName = {}) => {
29
29
  // If enabled (i.e. if SECRET_ARN is set in process.env) Fetch secrets from AWS Secrets Manager, and set them as environment variables.
30
30
  await secretsToEnv();
31
31
 
32
- // Helps mongoose reuse the connection. Lowers response times.
32
+ // Helps reuse the database connection. Lowers response times.
33
33
  context.callbackWaitsForEmptyEventLoop = false;
34
34
 
35
35
  // Run the Lambda
package/database/index.js CHANGED
@@ -1,65 +1,25 @@
1
- //todo: probably most of this file content can be removed
2
-
3
1
  /**
4
2
  * Database Module Index
5
- * Exports Mongoose models and connection utilities
3
+ * Exports Prisma client, connection utilities, and repositories
6
4
  *
7
5
  * Note: Frigg uses the Repository pattern for data access.
8
- * Models are not meant to be used directly - use repositories instead:
6
+ * Use repositories for data operations:
9
7
  * - SyncRepository (syncs/sync-repository.js)
10
8
  * - IntegrationRepository (integrations/integration-repository.js)
11
9
  * - CredentialRepository (credential/credential-repository.js)
12
10
  * etc.
13
11
  */
14
12
 
15
- // Lazy-load mongoose to avoid importing mongodb when using PostgreSQL only
16
- let _mongoose = null;
17
- let _IndividualUser = null;
18
- let _OrganizationUser = null;
19
- let _UserModel = null;
20
- let _WebsocketConnection = null;
21
-
22
- // Prisma exports (always available)
23
- const { prisma } = require('./prisma');
13
+ const { prisma, connectPrisma, disconnectPrisma } = require('./prisma');
24
14
  const { TokenRepository } = require('../token/repositories/token-repository');
25
15
  const {
26
16
  WebsocketConnectionRepository,
27
17
  } = require('../websocket/repositories/websocket-connection-repository');
28
18
 
29
19
  module.exports = {
30
- // Lazy-loaded mongoose exports (only load when accessed)
31
- get mongoose() {
32
- if (!_mongoose) {
33
- _mongoose = require('./mongoose').mongoose;
34
- }
35
- return _mongoose;
36
- },
37
- get IndividualUser() {
38
- if (!_IndividualUser) {
39
- _IndividualUser = require('./models/IndividualUser').IndividualUser;
40
- }
41
- return _IndividualUser;
42
- },
43
- get OrganizationUser() {
44
- if (!_OrganizationUser) {
45
- _OrganizationUser = require('./models/OrganizationUser').OrganizationUser;
46
- }
47
- return _OrganizationUser;
48
- },
49
- get UserModel() {
50
- if (!_UserModel) {
51
- _UserModel = require('./models/UserModel').UserModel;
52
- }
53
- return _UserModel;
54
- },
55
- get WebsocketConnection() {
56
- if (!_WebsocketConnection) {
57
- _WebsocketConnection = require('./models/WebsocketConnection').WebsocketConnection;
58
- }
59
- return _WebsocketConnection;
60
- },
61
- // Prisma (always available)
62
20
  prisma,
21
+ connectPrisma,
22
+ disconnectPrisma,
63
23
  TokenRepository,
64
24
  WebsocketConnectionRepository,
65
25
  };
@@ -49,17 +49,21 @@ class HealthCheckRepositoryDocumentDB extends HealthCheckRepositoryInterface {
49
49
  */
50
50
  async pingDatabase(maxTimeMS = 2000) {
51
51
  const pingStart = Date.now();
52
+ let timeoutId;
52
53
 
53
- const timeoutPromise = new Promise((_, reject) =>
54
- setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
55
- );
56
-
57
- await Promise.race([
58
- this.prisma.$runCommandRaw({ ping: 1 }),
59
- timeoutPromise,
60
- ]);
54
+ const timeoutPromise = new Promise((_, reject) => {
55
+ timeoutId = setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS);
56
+ });
61
57
 
62
- return Date.now() - pingStart;
58
+ try {
59
+ await Promise.race([
60
+ this.prisma.$runCommandRaw({ ping: 1 }),
61
+ timeoutPromise,
62
+ ]);
63
+ return Date.now() - pingStart;
64
+ } finally {
65
+ clearTimeout(timeoutId);
66
+ }
63
67
  }
64
68
 
65
69
  async createCredential(credentialData) {
@@ -1,4 +1,3 @@
1
- const { mongoose } = require('../mongoose');
2
1
  const {
3
2
  HealthCheckRepositoryInterface,
4
3
  } = require('./health-check-repository-interface');
@@ -19,7 +18,7 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
19
18
  async getDatabaseConnectionState() {
20
19
  let isConnected = false;
21
20
  let stateName = 'unknown';
22
-
21
+
23
22
  try {
24
23
  await this.prisma.$runCommandRaw({ ping: 1 });
25
24
  isConnected = true;
@@ -29,7 +28,6 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
29
28
  }
30
29
 
31
30
  return {
32
- readyState: isConnected ? 1 : 0,
33
31
  readyState: isConnected ? 1 : 0,
34
32
  stateName,
35
33
  isConnected,
@@ -38,22 +36,21 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
38
36
 
39
37
  async pingDatabase(maxTimeMS = 2000) {
40
38
  const pingStart = Date.now();
41
-
42
- // Create a timeout promise that rejects after maxTimeMS
43
- const timeoutPromise = new Promise((_, reject) =>
44
- setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS)
45
- );
46
-
47
- // Race between the database ping and the timeout
48
- await Promise.race([
49
- prisma.$queryRaw`SELECT 1`.catch(() => {
50
- // For MongoDB, use runCommandRaw instead
51
- return prisma.$runCommandRaw({ ping: 1 });
52
- }),
53
- timeoutPromise
54
- ]);
39
+ let timeoutId;
40
+
41
+ const timeoutPromise = new Promise((_, reject) => {
42
+ timeoutId = setTimeout(() => reject(new Error('Database ping timeout')), maxTimeMS);
43
+ });
55
44
 
56
- return Date.now() - pingStart;
45
+ try {
46
+ await Promise.race([
47
+ this.prisma.$runCommandRaw({ ping: 1 }),
48
+ timeoutPromise
49
+ ]);
50
+ return Date.now() - pingStart;
51
+ } finally {
52
+ clearTimeout(timeoutId);
53
+ }
57
54
  }
58
55
 
59
56
  async createCredential(credentialData) {
@@ -69,14 +66,17 @@ class HealthCheckRepositoryMongoDB extends HealthCheckRepositoryInterface {
69
66
  }
70
67
 
71
68
  /**
69
+ * Get raw credential from database bypassing Prisma encryption extension.
70
+ * Uses findRaw() to query MongoDB directly.
72
71
  * @param {string} id
73
72
  * @returns {Promise<Object|null>}
74
73
  */
75
74
  async getRawCredentialById(id) {
76
- const { ObjectId } = require('mongodb');
77
- return await mongoose.connection.db
78
- .collection('Credential')
79
- .findOne({ _id: new ObjectId(id) });
75
+ if (!id) return null;
76
+ const results = await this.prisma.credential.findRaw({
77
+ filter: { _id: { $oid: id } },
78
+ });
79
+ return results[0] || null;
80
80
  }
81
81
 
82
82
  async deleteCredential(id) {
@@ -5,11 +5,13 @@
5
5
  * handling the constraint that collections cannot be created inside
6
6
  * multi-document transactions.
7
7
  *
8
+ * Uses Prisma's $runCommandRaw to execute MongoDB admin commands.
9
+ *
8
10
  * @see https://github.com/prisma/prisma/issues/8305
9
11
  * @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
10
12
  */
11
13
 
12
- const { mongoose } = require('../mongoose');
14
+ const { prisma } = require('../prisma');
13
15
 
14
16
  /**
15
17
  * Ensures a MongoDB collection exists
@@ -30,20 +32,19 @@ const { mongoose } = require('../mongoose');
30
32
  */
31
33
  async function ensureCollectionExists(collectionName) {
32
34
  try {
33
- const collections = await mongoose.connection.db
34
- .listCollections({ name: collectionName })
35
- .toArray();
35
+ const result = await prisma.$runCommandRaw({
36
+ listCollections: 1,
37
+ filter: { name: collectionName },
38
+ });
39
+
40
+ const collections = result.cursor?.firstBatch || [];
36
41
 
37
42
  if (collections.length === 0) {
38
- // Collection doesn't exist, create it outside of any transaction
39
- await mongoose.connection.db.createCollection(collectionName);
43
+ await prisma.$runCommandRaw({ create: collectionName });
40
44
  console.log(`Created MongoDB collection: ${collectionName}`);
41
45
  }
42
46
  } catch (error) {
43
- // Collection might already exist due to race condition, or other error
44
- // Log warning but don't fail - let subsequent operations handle errors
45
47
  if (error.codeName === 'NamespaceExists') {
46
- // This is expected in race conditions, silently continue
47
48
  return;
48
49
  }
49
50
  console.warn(`Error ensuring collection ${collectionName} exists:`, error.message);
@@ -73,10 +74,12 @@ async function ensureCollectionsExist(collectionNames) {
73
74
  */
74
75
  async function collectionExists(collectionName) {
75
76
  try {
76
- const collections = await mongoose.connection.db
77
- .listCollections({ name: collectionName })
78
- .toArray();
77
+ const result = await prisma.$runCommandRaw({
78
+ listCollections: 1,
79
+ filter: { name: collectionName },
80
+ });
79
81
 
82
+ const collections = result.cursor?.firstBatch || [];
80
83
  return collections.length > 0;
81
84
  } catch (error) {
82
85
  console.error(`Error checking if collection ${collectionName} exists:`, error.message);
@@ -16,7 +16,7 @@
16
16
  * @see https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations
17
17
  */
18
18
 
19
- const { mongoose } = require('../mongoose');
19
+ const { prisma } = require('../prisma');
20
20
  const { ensureCollectionsExist } = require('./mongodb-collection-utils');
21
21
  const { getCollectionsFromSchemaSync } = require('./prisma-schema-parser');
22
22
  const config = require('../config');
@@ -53,8 +53,10 @@ async function initializeMongoDBSchema() {
53
53
  return;
54
54
  }
55
55
 
56
- // Check if database is connected
57
- if (mongoose.connection.readyState !== 1) {
56
+ // Verify database connectivity via Prisma ping
57
+ try {
58
+ await prisma.$runCommandRaw({ ping: 1 });
59
+ } catch (error) {
58
60
  throw new Error(
59
61
  'Cannot initialize MongoDB schema - database not connected. ' +
60
62
  'Call connectPrisma() before initializeMongoDBSchema()'
@@ -32,6 +32,7 @@ const createApp = (applyMiddleware) => {
32
32
  flushDebugLog(boomError);
33
33
  res.status(statusCode).json({ error: 'Internal Server Error' });
34
34
  } else {
35
+ console.warn(`[Frigg] ${req.method} ${req.path} -> ${statusCode}: ${err.message}`);
35
36
  res.status(statusCode).json({ error: err.message });
36
37
  }
37
38
  });
package/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  const {
2
- expectShallowEqualDbObject,
3
2
  get,
4
3
  getAll,
5
4
  verifyType,
@@ -14,17 +13,9 @@ const {
14
13
  createHandler,
15
14
  } = require('./core/index');
16
15
  const {
17
- mongoose,
18
- connectToDatabase,
19
- disconnectFromDatabase,
20
- createObjectId,
21
- IndividualUser,
22
- OrganizationUser,
23
- State,
24
- Token,
25
- UserModel,
26
- WebsocketConnection,
27
16
  prisma,
17
+ connectPrisma,
18
+ disconnectPrisma,
28
19
  TokenRepository,
29
20
  WebsocketConnectionRepository,
30
21
  } = require('./database/index');
@@ -95,13 +86,10 @@ const {
95
86
  const application = require('./application');
96
87
  const utils = require('./utils');
97
88
 
98
- // const {Sync } = require('./syncs/model');
99
-
100
89
  const { QueuerUtil } = require('./queues');
101
90
 
102
91
  module.exports = {
103
92
  // assertions
104
- expectShallowEqualDbObject,
105
93
  get,
106
94
  getAll,
107
95
  verifyType,
@@ -116,17 +104,9 @@ module.exports = {
116
104
  createHandler,
117
105
 
118
106
  // database
119
- mongoose,
120
- connectToDatabase,
121
- disconnectFromDatabase,
122
- createObjectId,
123
- IndividualUser,
124
- OrganizationUser,
125
- State,
126
- Token,
127
- UserModel,
128
- WebsocketConnection,
129
107
  prisma,
108
+ connectPrisma,
109
+ disconnectPrisma,
130
110
  TokenRepository,
131
111
  WebsocketConnectionRepository,
132
112
  createUserRepository,
package/modules/index.js CHANGED
@@ -1,4 +1,3 @@
1
- const { Entity } = require('./entity');
2
1
  const { ApiKeyRequester } = require('./requester/api-key');
3
2
  const { BasicAuthRequester } = require('./requester/basic');
4
3
  const { OAuth2Requester } = require('./requester/oauth-2');
@@ -7,7 +6,6 @@ const { ModuleConstants } = require('./ModuleConstants');
7
6
  const { ModuleFactory } = require('./module-factory');
8
7
 
9
8
  module.exports = {
10
- Entity,
11
9
  ApiKeyRequester,
12
10
  BasicAuthRequester,
13
11
  OAuth2Requester,
package/modules/module.js CHANGED
@@ -42,7 +42,9 @@ class Module extends Delegate {
42
42
  const apiParams = {
43
43
  ...this.definition.env,
44
44
  delegate: this,
45
- ...(this.credential?.data ? this.apiParamsFromCredential(this.credential.data) : {}), // Handle case when credential is undefined
45
+ ...(this.credential?.data
46
+ ? this.apiParamsFromCredential(this.credential.data)
47
+ : {}), // Handle case when credential is undefined
46
48
  ...this.apiParamsFromEntity(this.entity),
47
49
  };
48
50
  this.api = new this.apiClass(apiParams);
@@ -100,10 +102,15 @@ class Module extends Delegate {
100
102
  this.api,
101
103
  this.userId
102
104
  );
103
- Object.assign(
104
- credentialDetails.details,
105
- this.apiParamsFromCredential(this.api)
106
- );
105
+ const apiParams = this.apiParamsFromCredential(this.api);
106
+
107
+ if (!apiParams.refresh_token && this.api.isRefreshable) {
108
+ console.warn(
109
+ `[Frigg] No refresh_token in apiParams for module ${this.name}.`
110
+ );
111
+ }
112
+
113
+ Object.assign(credentialDetails.details, apiParams);
107
114
  credentialDetails.details.authIsValid = true;
108
115
 
109
116
  const persisted = await this.credentialRepository.upsertCredential(
@@ -30,7 +30,6 @@ const { ModuleConstants } = require('../ModuleConstants');
30
30
  * await api.getTokenFromClientCredentials();
31
31
  */
32
32
  class OAuth2Requester extends Requester {
33
-
34
33
  static requesterType = ModuleConstants.authType.oauth2;
35
34
 
36
35
  /**
@@ -118,6 +117,16 @@ class OAuth2Requester extends Requester {
118
117
  const newRefreshToken = get(params, 'refresh_token', null);
119
118
  if (newRefreshToken !== null) {
120
119
  this.refresh_token = newRefreshToken;
120
+ } else {
121
+ if (this.refresh_token) {
122
+ console.log(
123
+ '[Frigg] No refresh_token in response, preserving existing'
124
+ );
125
+ } else {
126
+ console.log(
127
+ '[Frigg] Current refresh_token is null and no new refresh_token in response'
128
+ );
129
+ }
121
130
  }
122
131
  const accessExpiresIn = get(params, 'expires_in', null);
123
132
  const refreshExpiresIn = get(
@@ -128,7 +137,9 @@ class OAuth2Requester extends Requester {
128
137
 
129
138
  this.accessTokenExpire = new Date(Date.now() + accessExpiresIn * 1000);
130
139
  if (refreshExpiresIn !== null) {
131
- this.refreshTokenExpire = new Date(Date.now() + refreshExpiresIn * 1000);
140
+ this.refreshTokenExpire = new Date(
141
+ Date.now() + refreshExpiresIn * 1000
142
+ );
132
143
  }
133
144
 
134
145
  await this.notify(this.DLGT_TOKEN_UPDATE);
@@ -237,6 +248,7 @@ class OAuth2Requester extends Requester {
237
248
  'Content-Type': 'application/x-www-form-urlencoded',
238
249
  },
239
250
  };
251
+ console.log('[Frigg] Refreshing access token with options');
240
252
  const response = await this._post(options, false);
241
253
  await this.setTokens(response);
242
254
  return response;
@@ -284,7 +296,7 @@ class OAuth2Requester extends Requester {
284
296
  */
285
297
  async refreshAuth() {
286
298
  try {
287
- console.log('[OAuth2Requester.refreshAuth] Starting token refresh', {
299
+ console.log('[Frigg] Starting token refresh', {
288
300
  grant_type: this.grant_type,
289
301
  has_refresh_token: !!this.refresh_token,
290
302
  has_client_id: !!this.client_id,
@@ -300,10 +312,10 @@ class OAuth2Requester extends Requester {
300
312
  } else {
301
313
  await this.getTokenFromClientCredentials();
302
314
  }
303
- console.log('[OAuth2Requester.refreshAuth] Token refresh succeeded');
315
+ console.log('[Frigg] Token refresh succeeded');
304
316
  return true;
305
317
  } catch (error) {
306
- console.error('[OAuth2Requester.refreshAuth] Token refresh failed', {
318
+ console.error('[Frigg] Token refresh failed', {
307
319
  error_message: error?.message,
308
320
  error_name: error?.name,
309
321
  response_status: error?.response?.status,
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0-next.70",
4
+ "version": "2.0.0-next.72",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
7
7
  "@aws-sdk/client-kms": "^3.588.0",
8
8
  "@aws-sdk/client-lambda": "^3.714.0",
9
+ "@aws-sdk/client-scheduler": "^3.1002.0",
9
10
  "@aws-sdk/client-sqs": "^3.588.0",
10
11
  "@hapi/boom": "^10.0.1",
11
12
  "bcryptjs": "^2.4.3",
@@ -20,7 +21,7 @@
20
21
  "fs-extra": "^11.2.0",
21
22
  "lodash": "4.17.21",
22
23
  "lodash.get": "^4.4.2",
23
- "mongoose": "6.11.6",
24
+ "mongodb": "^4.17.0",
24
25
  "node-fetch": "^2.6.7",
25
26
  "serverless-http": "^2.7.0",
26
27
  "uuid": "^9.0.1"
@@ -38,9 +39,9 @@
38
39
  }
39
40
  },
40
41
  "devDependencies": {
41
- "@friggframework/eslint-config": "2.0.0-next.70",
42
- "@friggframework/prettier-config": "2.0.0-next.70",
43
- "@friggframework/test": "2.0.0-next.70",
42
+ "@friggframework/eslint-config": "2.0.0-next.72",
43
+ "@friggframework/prettier-config": "2.0.0-next.72",
44
+ "@friggframework/test": "2.0.0-next.72",
44
45
  "@prisma/client": "^6.17.0",
45
46
  "@types/lodash": "4.17.15",
46
47
  "@typescript-eslint/eslint-plugin": "^8.0.0",
@@ -80,5 +81,5 @@
80
81
  "publishConfig": {
81
82
  "access": "public"
82
83
  },
83
- "gitHead": "dee1112300e01813e68b2598c3a722d1a31e1677"
84
+ "gitHead": "d655a19777f535775f2f67b5f17511eb438b300b"
84
85
  }
package/syncs/manager.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const moment = require('moment');
3
- const mongoose = require('mongoose');
3
+ const { ObjectId } = require('mongodb');
4
4
  const SyncObject = require('./sync');
5
5
  const { debug } = require('packages/logs');
6
6
  const { get } = require('../assertions');
@@ -314,7 +314,7 @@ class SyncManager {
314
314
 
315
315
  async createSyncDBObject(objArr, entities) {
316
316
  const entityIds = entities.map(
317
- (ent) => ({ $elemMatch: { $eq: mongoose.Types.ObjectId(ent) } })
317
+ (ent) => ({ $elemMatch: { $eq: new ObjectId(ent) } })
318
318
  // return {"$elemMatch": {"$eq": ent}};
319
319
  );
320
320
  const dataIdentifiers = [];
@@ -1,20 +1,3 @@
1
- declare module "@friggframework/associations/model" {
2
- import { Model } from "mongoose";
3
-
4
- export class Association extends Model {
5
- integrationId: string;
6
- name: string;
7
- type: string;
8
- primaryObject: string;
9
- objects: {
10
- entityId: string;
11
- objectType: string;
12
- objId: string;
13
- metadata?: object;
14
- }[];
15
- }
16
- }
17
-
18
1
  declare module "@friggframework/associations/association" {
19
2
  export default class Association implements IFriggAssociation {
20
3
  data: any;
@@ -1,3 +1,11 @@
1
- declare module "@friggframework/database/mongo" {
2
- export function connectToDatabase(): Promise<void>;
1
+ declare module "@friggframework/database" {
2
+ export const prisma: any;
3
+ export function connectPrisma(): Promise<any>;
4
+ export function disconnectPrisma(): Promise<void>;
5
+ export class TokenRepository {
6
+ constructor(params: { prismaClient: any });
7
+ }
8
+ export class WebsocketConnectionRepository {
9
+ constructor(params: { prismaClient: any });
10
+ }
3
11
  }
@@ -1,5 +1,7 @@
1
1
  declare module "@friggframework/encrypt" {
2
- import { Schema } from "mongoose";
3
-
4
- export function Encrypt(schema: Schema, options: any): void;
2
+ export class Cryptor {
3
+ constructor(params: { shouldUseAws?: boolean });
4
+ encrypt(plaintext: string): Promise<string>;
5
+ decrypt(ciphertext: string): Promise<string>;
6
+ }
5
7
  }
@@ -1,8 +1,7 @@
1
1
  declare module "@friggframework/integrations" {
2
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
3
- import { Model } from "mongoose";
4
3
 
5
- export class Integration extends Model {
4
+ export interface Integration {
6
5
  entities: any[];
7
6
  userId: string;
8
7
  status: string; // IntegrationStatus
@@ -1,20 +1,22 @@
1
1
  declare module "@friggframework/module-plugin" {
2
- import { Model } from "mongoose";
3
2
  import { Delegate, IFriggDelegate } from "@friggframework/core";
4
3
 
5
- export class Credential extends Model {
6
- userId: string;
7
- authIsValid: boolean;
8
- externalId: string;
4
+ export interface Credential {
5
+ id?: string;
6
+ userId?: string;
7
+ authIsValid?: boolean;
8
+ externalId?: string;
9
+ data?: any;
9
10
  }
10
11
 
11
- interface IFriggEntityManager { }
12
-
13
- export class Entity extends Model {
14
- credentialId: string;
15
- userId: string;
16
- name: string;
17
- externalId: string;
12
+ export interface Entity {
13
+ id?: string;
14
+ credentialId?: string;
15
+ userId?: string;
16
+ name?: string;
17
+ moduleName?: string;
18
+ externalId?: string;
19
+ data?: any;
18
20
  }
19
21
 
20
22
  export type MappedEntity = Entity & { id: string; type: any };
@@ -1,18 +1,3 @@
1
- declare module "@friggframework/syncs/model" {
2
- import { Model } from "mongoose";
3
-
4
- export class Sync extends Model {
5
- entities: any[];
6
- hash: string;
7
- name: string;
8
- dataIdentifiers: {
9
- entity: any;
10
- id: object;
11
- hash: string;
12
- }[];
13
- }
14
- }
15
-
16
1
  declare module "@friggframework/syncs/manager" {
17
2
  import Sync from "@friggframework/syncs/sync";
18
3
 
@@ -1,17 +0,0 @@
1
- const expectShallowEqualDbObject = (modelObject, compareObject) => {
2
- for (const key in compareObject) {
3
- let objVal = modelObject[key];
4
-
5
- if (objVal instanceof Date) {
6
- objVal = objVal.toISOString();
7
- } else if (objVal instanceof mongoose.Types.ObjectId) {
8
- objVal = objVal._id.toString();
9
- }
10
-
11
- expect(compareObject[key]).toBe(objVal);
12
- }
13
- };
14
-
15
- // TODO not sure how much this is needed, but could rewrite with _.isEqualWith for deep equality with custom checks.
16
-
17
- module.exports = { expectShallowEqualDbObject };
@@ -1,54 +0,0 @@
1
- const mongoose = require("mongoose");
2
-
3
- const schema = new mongoose.Schema({
4
- integration: {
5
- type: mongoose.Schema.Types.ObjectId,
6
- ref: "Integration",
7
- required: true,
8
- },
9
- name: { type: String, required: true },
10
- type: {
11
- type: String,
12
- enum: ["ONE_TO_MANY", "ONE_TO_ONE", "MANY_TO_ONE"],
13
- required: true,
14
- },
15
- primaryObject: { type: String, required: true },
16
- objects: [
17
- {
18
- entity: {
19
- type: mongoose.Schema.Types.ObjectId,
20
- ref: "Entity",
21
- required: true,
22
- },
23
- objectType: { type: String, required: true },
24
- objId: { type: String, required: true },
25
- metadata: { type: Object, required: false },
26
- },
27
- ],
28
- });
29
-
30
- schema.statics({
31
- addAssociation: async function (id, object) {
32
- return this.update({ _id: id }, { $push: { objects: object } });
33
- },
34
- findAssociation: async function (name, dataIdentifierHash) {
35
- const syncList = await this.list({
36
- name: name,
37
- "dataIdentifiers.hash": dataIdentifierHash,
38
- });
39
-
40
- if (syncList.length === 1) {
41
- return syncList[0];
42
- } else if (syncList.length === 0) {
43
- return null;
44
- } else {
45
- throw new Error(
46
- `there are multiple sync objects with the name ${name}, for entities [${entities}]`
47
- );
48
- }
49
- },
50
- });
51
-
52
- const Association =
53
- mongoose.models.Association || mongoose.model("Association", schema);
54
- module.exports = { Association };
@@ -1,76 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
- const bcrypt = require('bcryptjs');
3
- const { UserModel: Parent } = require('./UserModel');
4
-
5
- const collectionName = 'IndividualUser';
6
-
7
- const schema = new mongoose.Schema({
8
- email: { type: String },
9
- username: { type: String, unique: true },
10
- hashword: { type: String },
11
- appUserId: { type: String },
12
- organizationUser: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
13
- });
14
-
15
- schema.pre('save', async function () {
16
- if (this.hashword) {
17
- this.hashword = await bcrypt.hashSync(
18
- this.hashword,
19
- parseInt(this.schema.statics.decimals)
20
- )
21
- }
22
- })
23
-
24
- schema.static({
25
- decimals: 10,
26
- update: async function (id, options) {
27
- if ('password' in options) {
28
- options.hashword = await bcrypt.hashSync(
29
- options.password,
30
- parseInt(this.decimals)
31
- );
32
- delete options.password;
33
- }
34
- return this.findOneAndUpdate(
35
- {_id: id},
36
- options,
37
- {new: true, useFindAndModify: true}
38
- );
39
- },
40
- getUserByUsername: async function (username) {
41
- let getByUser;
42
- try{
43
- getByUser = await this.find({username});
44
- } catch (e) {
45
- console.log('oops')
46
- }
47
-
48
- if (getByUser.length > 1) {
49
- throw new Error(
50
- 'Unique username or email? Please reach out to our developers'
51
- );
52
- }
53
-
54
- if (getByUser.length === 1) {
55
- return getByUser[0];
56
- }
57
- },
58
- getUserByAppUserId: async function (appUserId) {
59
- const getByUser = await this.find({ appUserId });
60
-
61
- if (getByUser.length > 1) {
62
- throw new Error(
63
- 'Supposedly using a unique appUserId? Please reach out to our developers'
64
- );
65
- }
66
-
67
-
68
- if (getByUser.length === 1) {
69
- return getByUser[0];
70
- }
71
- }
72
- })
73
-
74
- const IndividualUser = Parent.discriminators?.IndividualUser || Parent.discriminator(collectionName, schema);
75
-
76
- module.exports = {IndividualUser};
@@ -1,29 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
- const { UserModel: Parent } = require('./UserModel');
3
-
4
- const collectionName = 'OrganizationUser';
5
-
6
- const schema = new mongoose.Schema({
7
- appOrgId: { type: String, required: true, unique: true },
8
- name: { type: String },
9
- });
10
-
11
- schema.static({
12
- getUserByAppOrgId: async function (appOrgId) {
13
- const getByUser = await this.find({ appOrgId });
14
-
15
- if (getByUser.length > 1) {
16
- throw new Error(
17
- 'Supposedly using a unique appOrgId? Please reach out to our developers'
18
- );
19
- }
20
-
21
- if (getByUser.length === 1) {
22
- return getByUser[0];
23
- }
24
- }
25
- })
26
-
27
- const OrganizationUser = Parent.discriminators?.OrganizationUser || Parent.discriminator(collectionName, schema);
28
-
29
- module.exports = {OrganizationUser};
@@ -1,7 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
-
3
- const schema = new mongoose.Schema({}, {timestamps: true})
4
-
5
- const UserModel = mongoose.models.User || mongoose.model('User',schema)
6
-
7
- module.exports = { UserModel: UserModel };
@@ -1,55 +0,0 @@
1
- const { mongoose } = require('../mongoose');
2
- const {
3
- ApiGatewayManagementApiClient,
4
- PostToConnectionCommand,
5
- } = require('@aws-sdk/client-apigatewaymanagementapi');
6
-
7
- const schema = new mongoose.Schema({
8
- connectionId: { type: mongoose.Schema.Types.String },
9
- });
10
-
11
- // Add a static method to get active connections
12
- schema.statics.getActiveConnections = async function () {
13
- try {
14
- // Return empty array if websockets are not configured
15
- if (!process.env.WEBSOCKET_API_ENDPOINT) {
16
- return [];
17
- }
18
-
19
- const connections = await this.find({}, 'connectionId');
20
- return connections.map((conn) => ({
21
- connectionId: conn.connectionId,
22
- send: async (data) => {
23
- const apigwManagementApi = new ApiGatewayManagementApiClient({
24
- endpoint: process.env.WEBSOCKET_API_ENDPOINT,
25
- });
26
-
27
- try {
28
- const command = new PostToConnectionCommand({
29
- ConnectionId: conn.connectionId,
30
- Data: JSON.stringify(data),
31
- });
32
- await apigwManagementApi.send(command);
33
- } catch (error) {
34
- if (error.statusCode === 410 || error.$metadata?.httpStatusCode === 410) {
35
- console.log(`Stale connection ${conn.connectionId}`);
36
- await this.deleteOne({
37
- connectionId: conn.connectionId,
38
- });
39
- } else {
40
- throw error;
41
- }
42
- }
43
- },
44
- }));
45
- } catch (error) {
46
- console.error('Error getting active connections:', error);
47
- throw error;
48
- }
49
- };
50
-
51
- const WebsocketConnection =
52
- mongoose.models.WebsocketConnection ||
53
- mongoose.model('WebsocketConnection', schema);
54
-
55
- module.exports = { WebsocketConnection };
@@ -1 +0,0 @@
1
- // todo: we need to get rid of this entire models folder
@@ -1,5 +0,0 @@
1
- const mongoose = require('mongoose');
2
- mongoose.set('strictQuery', false);
3
- module.exports = {
4
- mongoose
5
- }
@@ -1,108 +0,0 @@
1
- const { prisma } = require('../prisma');
2
- const { mongoose } = require('../mongoose');
3
- const {
4
- HealthCheckRepositoryInterface,
5
- } = require('./health-check-repository-interface');
6
-
7
- /**
8
- * Repository for Health Check database operations.
9
- * Provides atomic database operations for health testing.
10
- *
11
- * Follows DDD/Hexagonal Architecture:
12
- * - Infrastructure Layer (this repository)
13
- * - Pure database operations only, no business logic
14
- * - Used by Application Layer (Use Cases)
15
- *
16
- * Works identically for both MongoDB and PostgreSQL:
17
- * - Uses Prisma for database operations
18
- * - Encryption happens transparently via Prisma extension
19
- * - Both MongoDB and PostgreSQL use same Prisma API
20
- *
21
- * Migration from Mongoose to Prisma:
22
- * - Replaced Mongoose models with Prisma client
23
- * - Uses Credential model for encryption testing
24
- * - Maintains same method signatures for compatibility
25
- */
26
- class HealthCheckRepository extends HealthCheckRepositoryInterface {
27
- constructor() {
28
- super();
29
- }
30
-
31
- /**
32
- * Get database connection state
33
- * @returns {Object} Object with readyState, stateName, and isConnected
34
- */
35
- getDatabaseConnectionState() {
36
- const stateMap = {
37
- 0: 'disconnected',
38
- 1: 'connected',
39
- 2: 'connecting',
40
- 3: 'disconnecting',
41
- };
42
- const readyState = mongoose.connection.readyState;
43
-
44
- return {
45
- readyState,
46
- stateName: stateMap[readyState],
47
- isConnected: readyState === 1,
48
- };
49
- }
50
-
51
- /**
52
- * Ping the database to verify connectivity
53
- * @param {number} maxTimeMS - Maximum time to wait for ping response
54
- * @returns {Promise<number>} Response time in milliseconds
55
- * @throws {Error} If database is not connected or ping fails
56
- */
57
- async pingDatabase(maxTimeMS = 2000) {
58
- const pingStart = Date.now();
59
- await mongoose.connection.db.admin().ping({ maxTimeMS });
60
- return Date.now() - pingStart;
61
- }
62
-
63
- /**
64
- * Create a test credential for encryption testing
65
- * @param {Object} credentialData - Credential data to create
66
- * @returns {Promise<Object>} Created credential
67
- */
68
- async createCredential(credentialData) {
69
- return await prisma.credential.create({
70
- data: credentialData,
71
- });
72
- }
73
-
74
- /**
75
- * Find a credential by ID
76
- * @param {string} id - Credential ID
77
- * @returns {Promise<Object|null>} Found credential or null
78
- */
79
- async findCredentialById(id) {
80
- return await prisma.credential.findUnique({
81
- where: { id },
82
- });
83
- }
84
-
85
- /**
86
- * Get raw credential from database bypassing Prisma encryption extension
87
- * @param {string} id - Credential ID
88
- * @returns {Promise<Object|null>} Raw credential from database
89
- */
90
- async getRawCredentialById(id) {
91
- return await mongoose.connection.db
92
- .collection('credentials')
93
- .findOne({ _id: id });
94
- }
95
-
96
- /**
97
- * Delete a credential by ID
98
- * @param {string} id - Credential ID
99
- * @returns {Promise<void>}
100
- */
101
- async deleteCredential(id) {
102
- await prisma.credential.delete({
103
- where: { id },
104
- });
105
- }
106
- }
107
-
108
- module.exports = { HealthCheckRepository };
@@ -1,105 +0,0 @@
1
- const { mongoose } = require('../database/mongoose');
2
- const crypto = require('crypto');
3
-
4
- const hexPattern = /^[a-f0-9]+$/i; // match hex strings of length >= 1
5
-
6
- // Test that an encrypted secret value appears to have valid values (without actually decrypting it).
7
- function expectValidSecret(secret) {
8
- const parts = secret.split(':');
9
- const keyId = Buffer.from(parts[0], 'base64').toString();
10
- const iv = parts[1];
11
- const encryptedText = parts[2];
12
- const encryptedKey = Buffer.from(parts[3], 'base64').toString();
13
-
14
- expect(iv).toHaveLength(32);
15
- expect(iv).toMatch(hexPattern);
16
- expect(encryptedText).toHaveLength(14);
17
- expect(encryptedText).toMatch(hexPattern);
18
-
19
- // Keys from AWS start with Karn and have a different format.
20
- if (keyId.startsWith('arn:aws')) {
21
- expect(keyId).toBe(
22
- `arn:aws:kms:us-east-1:000000000000:key/${process.env.KMS_KEY_ARN}`
23
- );
24
- // The length here is a sanity check. Seems they are always within this range.
25
- expect(encryptedKey.length).toBeGreaterThanOrEqual(85);
26
- expect(encryptedKey.length).toBeLessThanOrEqual(140);
27
- } else {
28
- const { AES_KEY_ID, DEPRECATED_AES_KEY_ID } = process.env;
29
- expect([AES_KEY_ID, DEPRECATED_AES_KEY_ID]).toContain(keyId);
30
-
31
- const encryptedKeyParts = encryptedKey.split(':');
32
- const iv2 = encryptedKeyParts[0];
33
- const encryptedKeyPart = encryptedKeyParts[1];
34
-
35
- expect(iv2).toHaveLength(32);
36
- expect(iv2).toMatch(hexPattern);
37
- expect(encryptedKeyPart).toHaveLength(64);
38
- expect(encryptedKeyPart).toMatch(hexPattern);
39
- }
40
- }
41
-
42
- // Load and validate a raw test document compared to a Mongoose document object.
43
- async function expectValidRawDoc(Model, doc) {
44
- const rawDoc = await expectValidRawDocById(Model, doc._id);
45
-
46
- expect(rawDoc.notSecret.toString()).toBe(doc.notSecret.toString());
47
- expect(rawDoc).not.toHaveProperty('secret', doc.secret);
48
-
49
- return rawDoc;
50
- }
51
-
52
- // Load and validate a raw test document by ID.
53
- async function expectValidRawDocById(Model, _id) {
54
- const rawDoc = await Model.collection.findOne({ _id });
55
-
56
- expect(rawDoc).toHaveProperty('notSecret');
57
- expect(rawDoc).toHaveProperty('secret');
58
- expectValidSecret(rawDoc.secret);
59
-
60
- return rawDoc;
61
- }
62
-
63
- // Create a clean test model, so that the plug-in can be reinitialized.
64
- function createModel() {
65
- const randomHex = crypto.randomBytes(16).toString('hex');
66
- const schema = new mongoose.Schema({
67
- secret: { type: String, lhEncrypt: true },
68
- notSecret: { type: mongoose.Schema.Types.ObjectId },
69
- 'deeply.nested.secret': { type: String, lhEncrypt: true },
70
- });
71
-
72
- schema.plugin(Encrypt);
73
-
74
- const Model = mongoose.model(`EncryptTest_${randomHex}`, schema);
75
- return { schema, Model };
76
- }
77
-
78
- // Save and validate a test doc.
79
- async function saveTestDocument(Model) {
80
- const notSecret = new mongoose.Types.ObjectId();
81
- const secret = 'abcdefg';
82
- const doc = new Model({ notSecret, secret });
83
-
84
- expect(doc).toHaveProperty('notSecret');
85
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
86
- expect(doc).toHaveProperty('secret');
87
- expect(doc.secret).toBe(secret);
88
-
89
- await doc.save();
90
-
91
- expect(doc).toHaveProperty('notSecret');
92
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
93
- expect(doc).toHaveProperty('secret');
94
- expect(doc.secret).toBe(secret);
95
-
96
- return { doc, secret, notSecret };
97
- }
98
-
99
- module.exports = {
100
- expectValidSecret,
101
- expectValidRawDoc,
102
- expectValidRawDocById,
103
- createModel,
104
- saveTestDocument,
105
- };
package/modules/entity.js DELETED
@@ -1,46 +0,0 @@
1
- const { mongoose } = require('../database/mongoose');
2
- const schema = new mongoose.Schema(
3
- {
4
- credential: {
5
- type: mongoose.Schema.Types.ObjectId,
6
- ref: 'Credential',
7
- required: false,
8
- },
9
- user: {
10
- type: mongoose.Schema.Types.ObjectId,
11
- ref: 'User',
12
- required: false,
13
- },
14
- name: { type: String },
15
- moduleName: { type: String },
16
- externalId: { type: String },
17
- },
18
- { timestamps: true }
19
- );
20
-
21
- schema.static({
22
- findByUserId: async function (userId) {
23
- const entities = await this.find({ user: userId });
24
- if (entities.length === 0) {
25
- return null;
26
- } else if (entities.length === 1) {
27
- return entities[0];
28
- } else {
29
- throw new Error('multiple entities with same userId');
30
- }
31
- },
32
- findAllByUserId(userId) {
33
- return this.find({ user: userId });
34
- },
35
- upsert: async function (filter, obj) {
36
- return this.findOneAndUpdate(filter, obj, {
37
- new: true,
38
- upsert: true,
39
- setDefaultsOnInsert: true,
40
- });
41
- },
42
- });
43
-
44
- const Entity = mongoose.models.Entity || mongoose.model('Entity', schema);
45
-
46
- module.exports = { Entity };
package/syncs/model.js DELETED
@@ -1,62 +0,0 @@
1
- const mongoose = require("mongoose");
2
-
3
- const schema = new mongoose.Schema({
4
- entities: [
5
- { type: mongoose.Schema.Types.ObjectId, ref: "Entity", required: true },
6
- ],
7
- hash: { type: String, required: true },
8
- name: { type: String, required: true },
9
- dataIdentifiers: [
10
- {
11
- entity: {
12
- type: mongoose.Schema.Types.ObjectId,
13
- ref: "Entity",
14
- required: true,
15
- },
16
- id: { type: Object, required: true },
17
- hash: { type: String, required: true },
18
- },
19
- ],
20
- });
21
-
22
- schema.statics({
23
- getSyncObject: async function (name, dataIdentifier, entity) {
24
- // const syncList = await this.list({name:name,entities: {"$in": entities}, "entityIds.idHash":entityIdHash });
25
- const syncList = await this.find({
26
- name: name,
27
- dataIdentifiers: { $elemMatch: { id: dataIdentifier, entity } },
28
- });
29
-
30
- if (syncList.length === 1) {
31
- return syncList[0];
32
- } else if (syncList.length === 0) {
33
- return null;
34
- } else {
35
- throw new Error(
36
- `There are multiple sync objects with the name ${name}, for entities [${syncList[0].entities}] [${syncList[1].entities}]`
37
- );
38
- }
39
- },
40
-
41
- addDataIdentifier: async function (id, dataIdentifier) {
42
- return await this.update(
43
- { _id: id },
44
- {},
45
- { dataIdentifiers: dataIdentifier }
46
- );
47
- },
48
-
49
- getEntityObjIdForEntityIdFromObject: function (syncObj, entityId) {
50
- for (let dataIdentifier of syncObj.dataIdentifiers) {
51
- if (dataIdentifier.entity.toString() === entityId) {
52
- return dataIdentifier.id;
53
- }
54
- }
55
- throw new Error(
56
- `Sync object does not have DataIdentifier for entityId: ${entityId}`
57
- );
58
- },
59
- });
60
-
61
- const Sync = mongoose.models.Sync || mongoose.model("Sync", schema);
62
- module.exports = { Sync };