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

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ProcessRepository Interface
3
+ *
4
+ * Defines the contract for Process data access operations.
5
+ * Implementations must provide concrete methods for all operations.
6
+ *
7
+ * This interface supports the Hexagonal Architecture pattern by:
8
+ * - Defining clear boundaries between domain logic and data access
9
+ * - Allowing multiple implementations (MongoDB, PostgreSQL, in-memory)
10
+ * - Enabling dependency injection and testability
11
+ */
12
+ class ProcessRepositoryInterface {
13
+ /**
14
+ * Create a new process record
15
+ * @param {Object} processData - Process data to create
16
+ * @param {string} processData.userId - User ID
17
+ * @param {string} processData.integrationId - Integration ID
18
+ * @param {string} processData.name - Process name
19
+ * @param {string} processData.type - Process type
20
+ * @param {string} processData.state - Initial state
21
+ * @param {Object} [processData.context] - Process context
22
+ * @param {Object} [processData.results] - Process results
23
+ * @param {string[]} [processData.childProcesses] - Child process IDs
24
+ * @param {string} [processData.parentProcessId] - Parent process ID
25
+ * @returns {Promise<Object>} Created process record
26
+ */
27
+ async create(processData) {
28
+ throw new Error('Method create() must be implemented');
29
+ }
30
+
31
+ /**
32
+ * Find a process by ID
33
+ * @param {string} processId - Process ID to find
34
+ * @returns {Promise<Object|null>} Process record or null if not found
35
+ */
36
+ async findById(processId) {
37
+ throw new Error('Method findById() must be implemented');
38
+ }
39
+
40
+ /**
41
+ * Update a process record
42
+ * @param {string} processId - Process ID to update
43
+ * @param {Object} updates - Fields to update
44
+ * @returns {Promise<Object>} Updated process record
45
+ */
46
+ async update(processId, updates) {
47
+ throw new Error('Method update() must be implemented');
48
+ }
49
+
50
+ /**
51
+ * Find processes by integration and type
52
+ * @param {string} integrationId - Integration ID
53
+ * @param {string} type - Process type
54
+ * @returns {Promise<Array>} Array of process records
55
+ */
56
+ async findByIntegrationAndType(integrationId, type) {
57
+ throw new Error('Method findByIntegrationAndType() must be implemented');
58
+ }
59
+
60
+ /**
61
+ * Find active processes (not in excluded states)
62
+ * @param {string} integrationId - Integration ID
63
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
64
+ * @returns {Promise<Array>} Array of active process records
65
+ */
66
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
67
+ throw new Error('Method findActiveProcesses() must be implemented');
68
+ }
69
+
70
+ /**
71
+ * Find a process by name (most recent)
72
+ * @param {string} name - Process name
73
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
74
+ */
75
+ async findByName(name) {
76
+ throw new Error('Method findByName() must be implemented');
77
+ }
78
+
79
+ /**
80
+ * Delete a process by ID
81
+ * @param {string} processId - Process ID to delete
82
+ * @returns {Promise<void>}
83
+ */
84
+ async deleteById(processId) {
85
+ throw new Error('Method deleteById() must be implemented');
86
+ }
87
+ }
88
+
89
+ module.exports = { ProcessRepositoryInterface };
90
+
@@ -0,0 +1,190 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { ProcessRepositoryInterface } = require('./process-repository-interface');
3
+
4
+ /**
5
+ * MongoDB Process Repository Adapter
6
+ * Handles process persistence using Prisma with MongoDB
7
+ *
8
+ * MongoDB-specific characteristics:
9
+ * - Uses scalar fields for relations (userId, integrationId)
10
+ * - IDs are strings with @db.ObjectId
11
+ * - JSON fields for flexible context and results storage
12
+ * - Array field for childProcesses references
13
+ *
14
+ * Design Philosophy:
15
+ * - Generic Process model supports any type of long-running operation
16
+ * - Context and results stored as JSON for maximum flexibility
17
+ * - Integration-specific logic lives in use cases and services
18
+ */
19
+ class ProcessRepositoryMongo extends ProcessRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ }
24
+
25
+ /**
26
+ * Create a new process record
27
+ * @param {Object} processData - Process data to create
28
+ * @returns {Promise<Object>} Created process record
29
+ */
30
+ async create(processData) {
31
+ const process = await this.prisma.process.create({
32
+ data: {
33
+ userId: processData.userId,
34
+ integrationId: processData.integrationId,
35
+ name: processData.name,
36
+ type: processData.type,
37
+ state: processData.state || 'INITIALIZING',
38
+ context: processData.context || {},
39
+ results: processData.results || {},
40
+ childProcesses: processData.childProcesses || [],
41
+ parentProcessId: processData.parentProcessId || null,
42
+ },
43
+ });
44
+
45
+ return this._toPlainObject(process);
46
+ }
47
+
48
+ /**
49
+ * Find a process by ID
50
+ * @param {string} processId - Process ID to find
51
+ * @returns {Promise<Object|null>} Process record or null if not found
52
+ */
53
+ async findById(processId) {
54
+ const process = await this.prisma.process.findUnique({
55
+ where: { id: processId },
56
+ });
57
+
58
+ return process ? this._toPlainObject(process) : null;
59
+ }
60
+
61
+ /**
62
+ * Update a process record
63
+ * @param {string} processId - Process ID to update
64
+ * @param {Object} updates - Fields to update
65
+ * @returns {Promise<Object>} Updated process record
66
+ */
67
+ async update(processId, updates) {
68
+ // Prepare update data, excluding undefined values
69
+ const updateData = {};
70
+
71
+ if (updates.state !== undefined) {
72
+ updateData.state = updates.state;
73
+ }
74
+ if (updates.context !== undefined) {
75
+ updateData.context = updates.context;
76
+ }
77
+ if (updates.results !== undefined) {
78
+ updateData.results = updates.results;
79
+ }
80
+ if (updates.childProcesses !== undefined) {
81
+ updateData.childProcesses = updates.childProcesses;
82
+ }
83
+ if (updates.parentProcessId !== undefined) {
84
+ updateData.parentProcessId = updates.parentProcessId;
85
+ }
86
+
87
+ const process = await this.prisma.process.update({
88
+ where: { id: processId },
89
+ data: updateData,
90
+ });
91
+
92
+ return this._toPlainObject(process);
93
+ }
94
+
95
+ /**
96
+ * Find processes by integration and type
97
+ * @param {string} integrationId - Integration ID
98
+ * @param {string} type - Process type
99
+ * @returns {Promise<Array>} Array of process records
100
+ */
101
+ async findByIntegrationAndType(integrationId, type) {
102
+ const processes = await this.prisma.process.findMany({
103
+ where: {
104
+ integrationId,
105
+ type,
106
+ },
107
+ orderBy: {
108
+ createdAt: 'desc',
109
+ },
110
+ });
111
+
112
+ return processes.map((p) => this._toPlainObject(p));
113
+ }
114
+
115
+ /**
116
+ * Find active processes (not in excluded states)
117
+ * @param {string} integrationId - Integration ID
118
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
119
+ * @returns {Promise<Array>} Array of active process records
120
+ */
121
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
122
+ const processes = await this.prisma.process.findMany({
123
+ where: {
124
+ integrationId,
125
+ state: {
126
+ notIn: excludeStates,
127
+ },
128
+ },
129
+ orderBy: {
130
+ createdAt: 'desc',
131
+ },
132
+ });
133
+
134
+ return processes.map((p) => this._toPlainObject(p));
135
+ }
136
+
137
+ /**
138
+ * Find a process by name (most recent)
139
+ * @param {string} name - Process name
140
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
141
+ */
142
+ async findByName(name) {
143
+ const process = await this.prisma.process.findFirst({
144
+ where: { name },
145
+ orderBy: {
146
+ createdAt: 'desc',
147
+ },
148
+ });
149
+
150
+ return process ? this._toPlainObject(process) : null;
151
+ }
152
+
153
+ /**
154
+ * Delete a process by ID
155
+ * @param {string} processId - Process ID to delete
156
+ * @returns {Promise<void>}
157
+ */
158
+ async deleteById(processId) {
159
+ await this.prisma.process.delete({
160
+ where: { id: processId },
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Convert Prisma model to plain JavaScript object
166
+ * Ensures consistent API across repository implementations
167
+ * @private
168
+ * @param {Object} process - Prisma process model
169
+ * @returns {Object} Plain process object
170
+ */
171
+ _toPlainObject(process) {
172
+ return {
173
+ id: process.id,
174
+ userId: process.userId,
175
+ integrationId: process.integrationId,
176
+ name: process.name,
177
+ type: process.type,
178
+ state: process.state,
179
+ context: process.context,
180
+ results: process.results,
181
+ childProcesses: process.childProcesses,
182
+ parentProcessId: process.parentProcessId,
183
+ createdAt: process.createdAt,
184
+ updatedAt: process.updatedAt,
185
+ };
186
+ }
187
+ }
188
+
189
+ module.exports = { ProcessRepositoryMongo };
190
+
@@ -0,0 +1,194 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const { ProcessRepositoryInterface } = require('./process-repository-interface');
3
+
4
+ /**
5
+ * PostgreSQL Process Repository Adapter
6
+ * Handles process persistence using Prisma with PostgreSQL
7
+ *
8
+ * PostgreSQL-specific characteristics:
9
+ * - Uses foreign key constraints for relations
10
+ * - JSONB type for context and results (efficient querying)
11
+ * - Array type for childProcesses references
12
+ * - Transactional support available if needed
13
+ *
14
+ * Design Philosophy:
15
+ * - Same interface as MongoDB repository
16
+ * - Prisma abstracts away most database-specific details
17
+ * - Minor differences in JSON handling internally managed by Prisma
18
+ */
19
+ class ProcessRepositoryPostgres extends ProcessRepositoryInterface {
20
+ constructor() {
21
+ super();
22
+ this.prisma = prisma;
23
+ }
24
+
25
+ /**
26
+ * Create a new process record
27
+ * @param {Object} processData - Process data to create
28
+ * @returns {Promise<Object>} Created process record
29
+ */
30
+ async create(processData) {
31
+ const process = await this.prisma.process.create({
32
+ data: {
33
+ userId: processData.userId,
34
+ integrationId: processData.integrationId,
35
+ name: processData.name,
36
+ type: processData.type,
37
+ state: processData.state || 'INITIALIZING',
38
+ context: processData.context || {},
39
+ results: processData.results || {},
40
+ childProcesses: processData.childProcesses || [],
41
+ parentProcessId: processData.parentProcessId || null,
42
+ },
43
+ });
44
+
45
+ return this._toPlainObject(process);
46
+ }
47
+
48
+ /**
49
+ * Find a process by ID
50
+ * @param {string} processId - Process ID to find
51
+ * @returns {Promise<Object|null>} Process record or null if not found
52
+ */
53
+ async findById(processId) {
54
+ const process = await this.prisma.process.findUnique({
55
+ where: { id: processId },
56
+ });
57
+
58
+ return process ? this._toPlainObject(process) : null;
59
+ }
60
+
61
+ /**
62
+ * Update a process record
63
+ * @param {string} processId - Process ID to update
64
+ * @param {Object} updates - Fields to update
65
+ * @returns {Promise<Object>} Updated process record
66
+ */
67
+ async update(processId, updates) {
68
+ // Prepare update data, excluding undefined values
69
+ const updateData = {};
70
+
71
+ if (updates.state !== undefined) {
72
+ updateData.state = updates.state;
73
+ }
74
+ if (updates.context !== undefined) {
75
+ updateData.context = updates.context;
76
+ }
77
+ if (updates.results !== undefined) {
78
+ updateData.results = updates.results;
79
+ }
80
+ if (updates.childProcesses !== undefined) {
81
+ updateData.childProcesses = updates.childProcesses;
82
+ }
83
+ if (updates.parentProcessId !== undefined) {
84
+ updateData.parentProcessId = updates.parentProcessId;
85
+ }
86
+
87
+ const process = await this.prisma.process.update({
88
+ where: { id: processId },
89
+ data: updateData,
90
+ });
91
+
92
+ return this._toPlainObject(process);
93
+ }
94
+
95
+ /**
96
+ * Find processes by integration and type
97
+ * @param {string} integrationId - Integration ID
98
+ * @param {string} type - Process type
99
+ * @returns {Promise<Array>} Array of process records
100
+ */
101
+ async findByIntegrationAndType(integrationId, type) {
102
+ const processes = await this.prisma.process.findMany({
103
+ where: {
104
+ integrationId,
105
+ type,
106
+ },
107
+ orderBy: {
108
+ createdAt: 'desc',
109
+ },
110
+ });
111
+
112
+ return processes.map((p) => this._toPlainObject(p));
113
+ }
114
+
115
+ /**
116
+ * Find active processes (not in excluded states)
117
+ * @param {string} integrationId - Integration ID
118
+ * @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
119
+ * @returns {Promise<Array>} Array of active process records
120
+ */
121
+ async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
122
+ const processes = await this.prisma.process.findMany({
123
+ where: {
124
+ integrationId,
125
+ state: {
126
+ notIn: excludeStates,
127
+ },
128
+ },
129
+ orderBy: {
130
+ createdAt: 'desc',
131
+ },
132
+ });
133
+
134
+ return processes.map((p) => this._toPlainObject(p));
135
+ }
136
+
137
+ /**
138
+ * Find a process by name (most recent)
139
+ * @param {string} name - Process name
140
+ * @returns {Promise<Object|null>} Most recent process with given name, or null
141
+ */
142
+ async findByName(name) {
143
+ const process = await this.prisma.process.findFirst({
144
+ where: { name },
145
+ orderBy: {
146
+ createdAt: 'desc',
147
+ },
148
+ });
149
+
150
+ return process ? this._toPlainObject(process) : null;
151
+ }
152
+
153
+ /**
154
+ * Delete a process by ID
155
+ * @param {string} processId - Process ID to delete
156
+ * @returns {Promise<void>}
157
+ */
158
+ async deleteById(processId) {
159
+ await this.prisma.process.delete({
160
+ where: { id: processId },
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Convert Prisma model to plain JavaScript object
166
+ * Ensures consistent API across repository implementations
167
+ * @private
168
+ * @param {Object} process - Prisma process model
169
+ * @returns {Object} Plain process object
170
+ */
171
+ _toPlainObject(process) {
172
+ return {
173
+ id: String(process.id),
174
+ userId: String(process.userId),
175
+ integrationId: String(process.integrationId),
176
+ name: process.name,
177
+ type: process.type,
178
+ state: process.state,
179
+ context: process.context,
180
+ results: process.results,
181
+ childProcesses: Array.isArray(process.childProcesses)
182
+ ? (process.childProcesses.length > 0 && typeof process.childProcesses[0] === 'object' && process.childProcesses[0] !== null
183
+ ? process.childProcesses.map(child => String(child.id))
184
+ : process.childProcesses)
185
+ : [],
186
+ parentProcessId: process.parentProcessId !== null ? String(process.parentProcessId) : null,
187
+ createdAt: process.createdAt,
188
+ updatedAt: process.updatedAt,
189
+ };
190
+ }
191
+ }
192
+
193
+ module.exports = { ProcessRepositoryPostgres };
194
+
@@ -0,0 +1,128 @@
1
+ /**
2
+ * CreateProcess Use Case
3
+ *
4
+ * Creates a new process record for tracking long-running operations.
5
+ * Validates required fields and delegates persistence to the repository.
6
+ *
7
+ * Design Philosophy:
8
+ * - Use cases encapsulate business logic
9
+ * - Validation happens at the use case layer
10
+ * - Repositories handle only data access
11
+ * - Process model is generic and reusable
12
+ *
13
+ * @example
14
+ * const createProcess = new CreateProcess({ processRepository });
15
+ * const process = await createProcess.execute({
16
+ * userId: 'user123',
17
+ * integrationId: 'integration456',
18
+ * name: 'zoho-crm-contact-sync',
19
+ * type: 'CRM_SYNC',
20
+ * state: 'INITIALIZING',
21
+ * context: { syncType: 'INITIAL', totalRecords: 0 },
22
+ * results: { aggregateData: { totalSynced: 0, totalFailed: 0 } }
23
+ * });
24
+ */
25
+ class CreateProcess {
26
+ /**
27
+ * @param {Object} params
28
+ * @param {ProcessRepositoryInterface} params.processRepository - Repository for process data access
29
+ */
30
+ constructor({ processRepository }) {
31
+ if (!processRepository) {
32
+ throw new Error('processRepository is required');
33
+ }
34
+ this.processRepository = processRepository;
35
+ }
36
+
37
+ /**
38
+ * Execute the use case to create a process
39
+ * @param {Object} processData - Process data to create
40
+ * @param {string} processData.userId - User ID (required)
41
+ * @param {string} processData.integrationId - Integration ID (required)
42
+ * @param {string} processData.name - Process name (required)
43
+ * @param {string} processData.type - Process type (required)
44
+ * @param {string} [processData.state='INITIALIZING'] - Initial state
45
+ * @param {Object} [processData.context={}] - Process context
46
+ * @param {Object} [processData.results={}] - Process results
47
+ * @param {string[]} [processData.childProcesses=[]] - Child process IDs
48
+ * @param {string} [processData.parentProcessId] - Parent process ID
49
+ * @returns {Promise<Object>} Created process record
50
+ * @throws {Error} If validation fails or creation errors
51
+ */
52
+ async execute(processData) {
53
+ // Validate required fields
54
+ this._validateProcessData(processData);
55
+
56
+ // Set defaults for optional fields
57
+ const processToCreate = {
58
+ userId: processData.userId,
59
+ integrationId: processData.integrationId,
60
+ name: processData.name,
61
+ type: processData.type,
62
+ state: processData.state || 'INITIALIZING',
63
+ context: processData.context || {},
64
+ results: processData.results || {},
65
+ childProcesses: processData.childProcesses || [],
66
+ parentProcessId: processData.parentProcessId || null,
67
+ };
68
+
69
+ // Delegate to repository
70
+ try {
71
+ const createdProcess = await this.processRepository.create(processToCreate);
72
+ return createdProcess;
73
+ } catch (error) {
74
+ throw new Error(`Failed to create process: ${error.message}`);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Validate process data
80
+ * @private
81
+ * @param {Object} processData - Process data to validate
82
+ * @throws {Error} If validation fails
83
+ */
84
+ _validateProcessData(processData) {
85
+ const requiredFields = ['userId', 'integrationId', 'name', 'type'];
86
+ const missingFields = requiredFields.filter(field => !processData[field]);
87
+
88
+ if (missingFields.length > 0) {
89
+ throw new Error(
90
+ `Missing required fields for process creation: ${missingFields.join(', ')}`
91
+ );
92
+ }
93
+
94
+ // Validate field types
95
+ if (typeof processData.userId !== 'string') {
96
+ throw new Error('userId must be a string');
97
+ }
98
+ if (typeof processData.integrationId !== 'string') {
99
+ throw new Error('integrationId must be a string');
100
+ }
101
+ if (typeof processData.name !== 'string') {
102
+ throw new Error('name must be a string');
103
+ }
104
+ if (typeof processData.type !== 'string') {
105
+ throw new Error('type must be a string');
106
+ }
107
+
108
+ // Validate optional fields if provided
109
+ if (processData.state && typeof processData.state !== 'string') {
110
+ throw new Error('state must be a string');
111
+ }
112
+ if (processData.context && typeof processData.context !== 'object') {
113
+ throw new Error('context must be an object');
114
+ }
115
+ if (processData.results && typeof processData.results !== 'object') {
116
+ throw new Error('results must be an object');
117
+ }
118
+ if (processData.childProcesses && !Array.isArray(processData.childProcesses)) {
119
+ throw new Error('childProcesses must be an array');
120
+ }
121
+ if (processData.parentProcessId && typeof processData.parentProcessId !== 'string') {
122
+ throw new Error('parentProcessId must be a string');
123
+ }
124
+ }
125
+ }
126
+
127
+ module.exports = { CreateProcess };
128
+