@friggframework/core 2.0.0--canary.454.25d396a.0 → 2.0.0--canary.463.62579dd.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,34 +62,6 @@ npm install @friggframework/core
62
62
  yarn add @friggframework/core
63
63
  ```
64
64
 
65
- ### Prisma Support (Optional)
66
-
67
- `@friggframework/core` supports both MongoDB and PostgreSQL via Prisma ORM. **Prisma is an optional peer dependency** - you only need to install it if you're using database features that require migrations or schema generation.
68
-
69
- **When you need Prisma:**
70
- - Running database migrations (`prisma migrate`, `prisma db push`)
71
- - Generating Prisma clients for your application
72
- - Using the migration Lambda function (`dbMigrate`)
73
-
74
- **Installation:**
75
- ```bash
76
- # Install Prisma CLI and Client as dev dependencies
77
- npm install --save-dev prisma @prisma/client
78
-
79
- # Or with yarn
80
- yarn add -D prisma @prisma/client
81
- ```
82
-
83
- **Generate Prisma Clients:**
84
- ```bash
85
- # From @friggframework/core directory
86
- npm run prisma:generate:mongo # MongoDB only
87
- npm run prisma:generate:postgres # PostgreSQL only
88
- npm run prisma:generate # Both databases
89
- ```
90
-
91
- **Note:** The published npm package includes pre-generated Prisma clients, so you don't need to install Prisma just to use `@friggframework/core` in production. Prisma is only required if you're actively developing migrations or running the migration Lambda function.
92
-
93
65
  ### Prerequisites
94
66
 
95
67
  - Node.js 16+
@@ -77,9 +77,9 @@ const prismaClientSingleton = () => {
77
77
  let PrismaClient;
78
78
 
79
79
  if (config.DB_TYPE === 'mongodb') {
80
- PrismaClient = require('../generated/prisma-mongodb').PrismaClient;
80
+ PrismaClient = require('@prisma-mongodb/client').PrismaClient;
81
81
  } else if (config.DB_TYPE === 'postgresql') {
82
- PrismaClient = require('../generated/prisma-postgresql').PrismaClient;
82
+ PrismaClient = require('@prisma-postgresql/client').PrismaClient;
83
83
  } else {
84
84
  throw new Error(
85
85
  `Unsupported database type: ${config.DB_TYPE}. Supported values: 'mongodb', 'postgresql'`
@@ -19,6 +19,7 @@ const {
19
19
  const {
20
20
  getModulesDefinitionFromIntegrationClasses,
21
21
  } = require('../integrations/utils/map-integration-dto');
22
+ const { loadAppDefinition } = require('./app-definition-loader');
22
23
 
23
24
  const loadRouterFromObject = (IntegrationClass, routerObject) => {
24
25
  const router = Router();
@@ -50,37 +51,29 @@ const loadRouterFromObject = (IntegrationClass, routerObject) => {
50
51
  };
51
52
 
52
53
  const initializeRepositories = () => {
53
- const processRepository = createProcessRepository();
54
- const integrationRepository = createIntegrationRepository();
55
- const moduleRepository = createModuleRepository();
56
-
57
- return { processRepository, integrationRepository, moduleRepository };
58
- };
59
-
60
- const createModuleFactoryWithDefinitions = (
61
- moduleRepository,
62
- integrationClasses
63
- ) => {
64
- const moduleDefinitions =
65
- getModulesDefinitionFromIntegrationClasses(integrationClasses);
66
-
67
- return new ModuleFactory({
68
- moduleRepository,
69
- moduleDefinitions,
70
- });
54
+ return {
55
+ processRepository: createProcessRepository(),
56
+ integrationRepository: createIntegrationRepository(),
57
+ moduleRepository: createModuleRepository(),
58
+ };
71
59
  };
72
60
 
61
+ /**
62
+ * Load hydrated integration instance for webhook events WITH integrationId.
63
+ * Must load app definition to find all integration classes, then match
64
+ * against the integration record's type.
65
+ */
73
66
  const loadIntegrationForWebhook = async (integrationId) => {
74
- const { loadAppDefinition } = require('./app-definition-loader');
75
67
  const { integrations: integrationClasses } = loadAppDefinition();
68
+ const { integrationRepository, moduleRepository } = initializeRepositories();
76
69
 
77
- const { integrationRepository, moduleRepository } =
78
- initializeRepositories();
79
-
80
- const moduleFactory = createModuleFactoryWithDefinitions(
81
- moduleRepository,
70
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses(
82
71
  integrationClasses
83
72
  );
73
+ const moduleFactory = new ModuleFactory({
74
+ moduleRepository,
75
+ moduleDefinitions,
76
+ });
84
77
 
85
78
  const getIntegrationInstance = new GetIntegrationInstance({
86
79
  integrationRepository,
@@ -92,19 +85,42 @@ const loadIntegrationForWebhook = async (integrationId) => {
92
85
  integrationId
93
86
  );
94
87
 
88
+ if (!integrationRecord) {
89
+ throw new Error(
90
+ `No integration found by the ID of ${integrationId}`
91
+ );
92
+ }
93
+
95
94
  return await getIntegrationInstance.execute(
96
95
  integrationId,
97
96
  integrationRecord.userId
98
97
  );
99
98
  };
100
99
 
100
+ /**
101
+ * Load hydrated integration instance for process-based events.
102
+ * Integration class is already known from queue worker context,
103
+ * so we receive it as a parameter rather than loading all classes.
104
+ */
101
105
  const loadIntegrationForProcess = async (processId, integrationClass) => {
106
+ if (!integrationClass) {
107
+ throw new Error('integrationClass parameter is required');
108
+ }
109
+
110
+ if (!processId) {
111
+ throw new Error('processId is required in queue message data');
112
+ }
113
+
102
114
  const { processRepository, integrationRepository, moduleRepository } =
103
115
  initializeRepositories();
104
116
 
105
- const moduleFactory = createModuleFactoryWithDefinitions(moduleRepository, [
117
+ const moduleDefinitions = getModulesDefinitionFromIntegrationClasses([
106
118
  integrationClass,
107
119
  ]);
120
+ const moduleFactory = new ModuleFactory({
121
+ moduleRepository,
122
+ moduleDefinitions,
123
+ });
108
124
 
109
125
  const getIntegrationInstance = new GetIntegrationInstance({
110
126
  integrationRepository,
@@ -112,10 +128,6 @@ const loadIntegrationForProcess = async (processId, integrationClass) => {
112
128
  moduleFactory,
113
129
  });
114
130
 
115
- if (!processId) {
116
- throw new Error('processId is required in queue message data');
117
- }
118
-
119
131
  const process = await processRepository.findById(processId);
120
132
 
121
133
  if (!process) {
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@friggframework/core",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.454.25d396a.0",
4
+ "version": "2.0.0--canary.463.62579dd.0",
5
5
  "dependencies": {
6
6
  "@hapi/boom": "^10.0.1",
7
- "aws-sdk": "^2.1200.0",
7
+ "@prisma/client": "^6.16.3",
8
8
  "bcryptjs": "^2.4.3",
9
9
  "body-parser": "^1.20.2",
10
- "chalk": "^4.1.2",
11
10
  "common-tags": "^1.8.2",
12
11
  "cors": "^2.8.5",
13
12
  "dotenv": "^16.4.7",
@@ -23,22 +22,17 @@
23
22
  "uuid": "^9.0.1"
24
23
  },
25
24
  "peerDependencies": {
26
- "@prisma/client": "^6.16.3",
27
- "prisma": "^6.16.3"
25
+ "aws-sdk": "^2.1200.0"
28
26
  },
29
27
  "peerDependenciesMeta": {
30
- "@prisma/client": {
31
- "optional": true
32
- },
33
- "prisma": {
28
+ "aws-sdk": {
34
29
  "optional": true
35
30
  }
36
31
  },
37
32
  "devDependencies": {
38
- "@friggframework/eslint-config": "2.0.0--canary.454.25d396a.0",
39
- "@friggframework/prettier-config": "2.0.0--canary.454.25d396a.0",
40
- "@friggframework/test": "2.0.0--canary.454.25d396a.0",
41
- "@prisma/client": "^6.17.0",
33
+ "@friggframework/eslint-config": "2.0.0--canary.463.62579dd.0",
34
+ "@friggframework/prettier-config": "2.0.0--canary.463.62579dd.0",
35
+ "@friggframework/test": "2.0.0--canary.463.62579dd.0",
42
36
  "@types/lodash": "4.17.15",
43
37
  "@typescript-eslint/eslint-plugin": "^8.0.0",
44
38
  "chai": "^4.3.6",
@@ -48,7 +42,7 @@
48
42
  "eslint-plugin-promise": "^7.0.0",
49
43
  "jest": "^29.7.0",
50
44
  "prettier": "^2.7.1",
51
- "prisma": "^6.17.0",
45
+ "prisma": "^6.16.3",
52
46
  "sinon": "^16.1.1",
53
47
  "typescript": "^5.0.2"
54
48
  },
@@ -60,7 +54,7 @@
60
54
  "prisma:generate": "npm run prisma:generate:mongo && npm run prisma:generate:postgres",
61
55
  "prisma:push:mongo": "npx prisma db push --schema ./prisma-mongodb/schema.prisma",
62
56
  "prisma:migrate:postgres": "npx prisma migrate dev --schema ./prisma-postgresql/schema.prisma",
63
- "prepublishOnly": "npm run prisma:generate"
57
+ "postinstall": "npm run prisma:generate"
64
58
  },
65
59
  "author": "",
66
60
  "license": "MIT",
@@ -77,5 +71,5 @@
77
71
  "publishConfig": {
78
72
  "access": "public"
79
73
  },
80
- "gitHead": "25d396ab0d73094bfab4853f9ff15a5500174ca7"
74
+ "gitHead": "62579dd537cdc5d96c3d36e2441e56778c3a160a"
81
75
  }
@@ -3,10 +3,8 @@
3
3
  // Migration from Mongoose ODM to Prisma ORM
4
4
 
5
5
  generator client {
6
- provider = "prisma-client-js"
7
- output = "../generated/prisma-mongodb"
8
- binaryTargets = ["native", "rhel-openssl-3.0.x"] // native for local dev, rhel for Lambda deployment
9
- engineType = "binary" // Use binary engines (smaller size)
6
+ provider = "prisma-client-js"
7
+ output = "../node_modules/@prisma-mongodb/client"
10
8
  }
11
9
 
12
10
  datasource db {
@@ -21,8 +19,8 @@ datasource db {
21
19
  /// User model with discriminator pattern support
22
20
  /// Replaces Mongoose discriminators (IndividualUser, OrganizationUser)
23
21
  model User {
24
- id String @id @default(auto()) @map("_id") @db.ObjectId
25
- type UserType
22
+ id String @id @default(auto()) @map("_id") @db.ObjectId
23
+ type UserType
26
24
 
27
25
  // Timestamps
28
26
  createdAt DateTime @default(now())
@@ -89,12 +87,12 @@ model Token {
89
87
  /// OAuth credentials and API tokens
90
88
  /// All sensitive data encrypted with KMS at rest
91
89
  model Credential {
92
- id String @id @default(auto()) @map("_id") @db.ObjectId
93
- userId String? @db.ObjectId
94
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
95
- subType String?
90
+ id String @id @default(auto()) @map("_id") @db.ObjectId
91
+ userId String? @db.ObjectId
92
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
93
+ subType String?
96
94
  authIsValid Boolean?
97
- externalId String?
95
+ externalId String?
98
96
 
99
97
  // Dynamic OAuth fields stored as JSON (encrypted via Prisma middleware)
100
98
  // Contains: access_token, refresh_token, domain, expires_in, token_type, etc.
@@ -127,11 +125,11 @@ model Entity {
127
125
  updatedAt DateTime @updatedAt
128
126
 
129
127
  // Relations - many-to-many with scalar lists
130
- integrations Integration[] @relation("IntegrationEntities", fields: [integrationIds], references: [id])
131
- integrationIds String[] @db.ObjectId
128
+ integrations Integration[] @relation("IntegrationEntities", fields: [integrationIds], references: [id])
129
+ integrationIds String[] @db.ObjectId
132
130
 
133
- syncs Sync[] @relation("SyncEntities", fields: [syncIds], references: [id])
134
- syncIds String[] @db.ObjectId
131
+ syncs Sync[] @relation("SyncEntities", fields: [syncIds], references: [id])
132
+ syncIds String[] @db.ObjectId
135
133
 
136
134
  dataIdentifiers DataIdentifier[]
137
135
  associationObjects AssociationObject[]
@@ -155,7 +153,7 @@ model Integration {
155
153
  status IntegrationStatus @default(ENABLED)
156
154
 
157
155
  // Configuration and version
158
- config Json? // Integration configuration object
156
+ config Json? // Integration configuration object
159
157
  version String?
160
158
 
161
159
  // Entity references (many-to-many via explicit scalar list)
@@ -322,11 +320,11 @@ model Association {
322
320
  /// Association object entry
323
321
  /// Replaces nested array structure in Mongoose
324
322
  model AssociationObject {
325
- id String @id @default(auto()) @map("_id") @db.ObjectId
326
- associationId String @db.ObjectId
327
- association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)
328
- entityId String @db.ObjectId
329
- entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
323
+ id String @id @default(auto()) @map("_id") @db.ObjectId
324
+ associationId String @db.ObjectId
325
+ association Association @relation(fields: [associationId], references: [id], onDelete: Cascade)
326
+ entityId String @db.ObjectId
327
+ entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
330
328
  objectType String
331
329
  objId String
332
330
  metadata Json? // Optional metadata
@@ -361,4 +359,4 @@ model WebsocketConnection {
361
359
 
362
360
  @@index([connectionId])
363
361
  @@map("WebsocketConnection")
364
- }
362
+ }
@@ -3,10 +3,8 @@
3
3
  // Converted from MongoDB schema for relational database support
4
4
 
5
5
  generator client {
6
- provider = "prisma-client-js"
7
- output = "../generated/prisma-postgresql"
8
- binaryTargets = ["native", "rhel-openssl-3.0.x"] // native for local dev, rhel for Lambda deployment
9
- engineType = "binary" // Use binary engines (smaller size)
6
+ provider = "prisma-client-js"
7
+ output = "../node_modules/@prisma-postgresql/client"
10
8
  }
11
9
 
12
10
  datasource db {
@@ -21,8 +19,8 @@ datasource db {
21
19
  /// User model with discriminator pattern support
22
20
  /// Replaces Mongoose discriminators (IndividualUser, OrganizationUser)
23
21
  model User {
24
- id Int @id @default(autoincrement())
25
- type UserType
22
+ id Int @id @default(autoincrement())
23
+ type UserType
26
24
 
27
25
  // Timestamps
28
26
  createdAt DateTime @default(now())
@@ -87,12 +85,12 @@ model Token {
87
85
  /// OAuth credentials and API tokens
88
86
  /// All sensitive data encrypted with KMS at rest
89
87
  model Credential {
90
- id Int @id @default(autoincrement())
91
- userId Int?
92
- user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
93
- subType String?
88
+ id Int @id @default(autoincrement())
89
+ userId Int?
90
+ user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
91
+ subType String?
94
92
  authIsValid Boolean?
95
- externalId String?
93
+ externalId String?
96
94
 
97
95
  // Dynamic OAuth fields stored as JSON (encrypted via Prisma middleware)
98
96
  // Contains: access_token, refresh_token, domain, expires_in, token_type, etc.
@@ -148,7 +146,7 @@ model Integration {
148
146
  status IntegrationStatus @default(ENABLED)
149
147
 
150
148
  // Configuration and version
151
- config Json? // Integration configuration object
149
+ config Json? // Integration configuration object
152
150
  version String?
153
151
 
154
152
  // Entity references (many-to-many via implicit join table)
@@ -227,11 +225,11 @@ model Sync {
227
225
  /// Data identifier for sync operations
228
226
  /// Replaces nested array structure in Mongoose
229
227
  model DataIdentifier {
230
- id Int @id @default(autoincrement())
228
+ id Int @id @default(autoincrement())
231
229
  syncId Int?
232
- sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)
230
+ sync Sync? @relation(fields: [syncId], references: [id], onDelete: Cascade)
233
231
  entityId Int
234
- entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
232
+ entity Entity @relation(fields: [entityId], references: [id], onDelete: Cascade)
235
233
 
236
234
  // Identifier data (can be any structure)
237
235
  idData Json
@@ -334,7 +332,7 @@ model Process {
334
332
 
335
333
  /// Generic state storage
336
334
  model State {
337
- id Int @id @default(autoincrement())
335
+ id Int @id @default(autoincrement())
338
336
  state Json?
339
337
  }
340
338
 
@@ -1,137 +0,0 @@
1
- /**
2
- * Run Database Migration Use Case
3
- *
4
- * Business logic for running Prisma database migrations.
5
- * Orchestrates Prisma client generation and migration execution.
6
- *
7
- * This use case follows the Frigg hexagonal architecture pattern where:
8
- * - Handlers (adapters) call use cases
9
- * - Use cases contain business logic and orchestration
10
- * - Use cases call repositories/utilities for data access
11
- */
12
-
13
- class RunDatabaseMigrationUseCase {
14
- /**
15
- * @param {Object} dependencies
16
- * @param {Object} dependencies.prismaRunner - Prisma runner utilities
17
- */
18
- constructor({ prismaRunner }) {
19
- if (!prismaRunner) {
20
- throw new Error('prismaRunner dependency is required');
21
- }
22
- this.prismaRunner = prismaRunner;
23
- }
24
-
25
- /**
26
- * Execute database migration
27
- *
28
- * @param {Object} params
29
- * @param {string} params.dbType - Database type ('postgresql' or 'mongodb')
30
- * @param {string} params.stage - Deployment stage (determines migration command)
31
- * @param {boolean} [params.verbose=false] - Enable verbose output
32
- * @returns {Promise<Object>} Migration result { success, dbType, stage, command, message }
33
- * @throws {MigrationError} If migration fails
34
- * @throws {ValidationError} If parameters are invalid
35
- */
36
- async execute({ dbType, stage, verbose = false }) {
37
- // Validation
38
- this._validateParams({ dbType, stage });
39
-
40
- // Step 1: Generate Prisma client
41
- const generateResult = await this.prismaRunner.runPrismaGenerate(dbType, verbose);
42
-
43
- if (!generateResult.success) {
44
- throw new MigrationError(
45
- `Failed to generate Prisma client: ${generateResult.error || 'Unknown error'}`,
46
- { dbType, stage, step: 'generate', output: generateResult.output }
47
- );
48
- }
49
-
50
- // Step 2: Run migrations based on database type
51
- let migrationResult;
52
- let migrationCommand;
53
-
54
- if (dbType === 'postgresql') {
55
- migrationCommand = this.prismaRunner.getMigrationCommand(stage);
56
- migrationResult = await this.prismaRunner.runPrismaMigrate(migrationCommand, verbose);
57
-
58
- if (!migrationResult.success) {
59
- throw new MigrationError(
60
- `PostgreSQL migration failed: ${migrationResult.error || 'Unknown error'}`,
61
- { dbType, stage, command: migrationCommand, step: 'migrate', output: migrationResult.output }
62
- );
63
- }
64
- } else if (dbType === 'mongodb') {
65
- migrationCommand = 'db push';
66
- // Use non-interactive mode for automated/Lambda environments
67
- migrationResult = await this.prismaRunner.runPrismaDbPush(verbose, true);
68
-
69
- if (!migrationResult.success) {
70
- throw new MigrationError(
71
- `MongoDB push failed: ${migrationResult.error || 'Unknown error'}`,
72
- { dbType, stage, command: migrationCommand, step: 'push', output: migrationResult.output }
73
- );
74
- }
75
- } else {
76
- throw new ValidationError(`Unsupported database type: ${dbType}. Must be 'postgresql' or 'mongodb'.`);
77
- }
78
-
79
- // Return success result
80
- return {
81
- success: true,
82
- dbType,
83
- stage,
84
- command: migrationCommand,
85
- message: 'Database migration completed successfully',
86
- };
87
- }
88
-
89
- /**
90
- * Validate execution parameters
91
- * @private
92
- */
93
- _validateParams({ dbType, stage }) {
94
- if (!dbType) {
95
- throw new ValidationError('dbType is required');
96
- }
97
-
98
- if (typeof dbType !== 'string') {
99
- throw new ValidationError('dbType must be a string');
100
- }
101
-
102
- if (!stage) {
103
- throw new ValidationError('stage is required');
104
- }
105
-
106
- if (typeof stage !== 'string') {
107
- throw new ValidationError('stage must be a string');
108
- }
109
- }
110
- }
111
-
112
- /**
113
- * Custom error for migration failures
114
- */
115
- class MigrationError extends Error {
116
- constructor(message, context = {}) {
117
- super(message);
118
- this.name = 'MigrationError';
119
- this.context = context;
120
- }
121
- }
122
-
123
- /**
124
- * Custom error for validation failures
125
- */
126
- class ValidationError extends Error {
127
- constructor(message) {
128
- super(message);
129
- this.name = 'ValidationError';
130
- }
131
- }
132
-
133
- module.exports = {
134
- RunDatabaseMigrationUseCase,
135
- MigrationError,
136
- ValidationError,
137
- };