@friggframework/core 2.0.0--canary.611.3311e90.0 → 2.0.0--canary.517.ff03f2c.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.
Files changed (53) hide show
  1. package/admin-scripts/index.js +52 -0
  2. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  3. package/admin-scripts/repositories/admin-process-repository-factory.js +51 -0
  4. package/admin-scripts/repositories/admin-process-repository-interface.js +165 -0
  5. package/admin-scripts/repositories/admin-process-repository-mongo.js +227 -0
  6. package/admin-scripts/repositories/admin-process-repository-postgres.js +265 -0
  7. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  8. package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
  9. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  10. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  11. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  12. package/application/commands/admin-script-commands.js +356 -0
  13. package/application/commands/integration-commands.js +34 -1
  14. package/database/use-cases/check-database-state-use-case.js +2 -2
  15. package/database/use-cases/trigger-database-migration-use-case.js +1 -1
  16. package/generated/prisma-mongodb/edge.js +50 -5
  17. package/generated/prisma-mongodb/index-browser.js +40 -2
  18. package/generated/prisma-mongodb/index.d.ts +2778 -53
  19. package/generated/prisma-mongodb/index.js +50 -5
  20. package/generated/prisma-mongodb/package.json +1 -1
  21. package/generated/prisma-mongodb/schema.prisma +72 -1
  22. package/generated/prisma-mongodb/wasm.js +50 -5
  23. package/generated/prisma-postgresql/edge.js +49 -5
  24. package/generated/prisma-postgresql/index-browser.js +39 -2
  25. package/generated/prisma-postgresql/index.d.ts +3417 -84
  26. package/generated/prisma-postgresql/index.js +49 -5
  27. package/generated/prisma-postgresql/package.json +1 -1
  28. package/generated/prisma-postgresql/schema.prisma +71 -1
  29. package/generated/prisma-postgresql/wasm.js +49 -5
  30. package/handlers/app-definition-loader.js +9 -4
  31. package/handlers/backend-utils.js +33 -14
  32. package/handlers/middleware/admin-auth.js +73 -0
  33. package/handlers/routers/db-migration.handler.js +1 -1
  34. package/handlers/routers/db-migration.js +18 -33
  35. package/integrations/integration-base.js +150 -35
  36. package/integrations/integration-router.js +2 -1
  37. package/integrations/repositories/config-patch-shared.js +43 -0
  38. package/integrations/repositories/integration-repository-documentdb.js +54 -1
  39. package/integrations/repositories/integration-repository-interface.js +15 -0
  40. package/integrations/repositories/integration-repository-mongo.js +48 -0
  41. package/integrations/repositories/integration-repository-postgres.js +28 -0
  42. package/integrations/tests/doubles/dummy-integration-class.js +8 -0
  43. package/integrations/tests/doubles/test-integration-repository.js +24 -1
  44. package/integrations/use-cases/create-integration.js +138 -6
  45. package/integrations/use-cases/delete-integration-for-user.js +19 -1
  46. package/integrations/use-cases/patch-integration-config.js +39 -0
  47. package/integrations/use-cases/update-integration-config.js +32 -0
  48. package/package.json +5 -5
  49. package/prisma-mongodb/schema.prisma +72 -1
  50. package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
  51. package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
  52. package/prisma-postgresql/schema.prisma +71 -1
  53. package/reporting/use-cases/list-integrations-report.js +2 -0
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Admin Scripts Module
3
+ *
4
+ * Exports repository interfaces and factories for admin script management.
5
+ * Concrete implementations support MongoDB, PostgreSQL, and DocumentDB.
6
+ *
7
+ * Repository interfaces follow the Port pattern in Hexagonal Architecture:
8
+ * - Define contracts for data access
9
+ * - Enable dependency injection
10
+ * - Allow testing with mocks
11
+ * - Support multiple database implementations
12
+ *
13
+ * Authentication:
14
+ * - Uses ENV-based ADMIN_API_KEY (see handlers/middleware/admin-auth.js)
15
+ * - No database-backed API keys (simplified from original design)
16
+ */
17
+
18
+ // Repository Interfaces
19
+ const { AdminProcessRepositoryInterface } = require('./repositories/admin-process-repository-interface');
20
+ const { ScriptScheduleRepositoryInterface } = require('./repositories/script-schedule-repository-interface');
21
+
22
+ // Repository Factories
23
+ const {
24
+ createAdminProcessRepository,
25
+ AdminProcessRepositoryMongo,
26
+ AdminProcessRepositoryPostgres,
27
+ AdminProcessRepositoryDocumentDB,
28
+ } = require('./repositories/admin-process-repository-factory');
29
+ const {
30
+ createScriptScheduleRepository,
31
+ ScriptScheduleRepositoryMongo,
32
+ ScriptScheduleRepositoryPostgres,
33
+ ScriptScheduleRepositoryDocumentDB,
34
+ } = require('./repositories/script-schedule-repository-factory');
35
+
36
+ module.exports = {
37
+ // Repository Interfaces
38
+ AdminProcessRepositoryInterface,
39
+ ScriptScheduleRepositoryInterface,
40
+
41
+ // Repository Factories (primary exports for use cases)
42
+ createAdminProcessRepository,
43
+ createScriptScheduleRepository,
44
+
45
+ // Concrete Implementations (for testing)
46
+ AdminProcessRepositoryMongo,
47
+ AdminProcessRepositoryPostgres,
48
+ AdminProcessRepositoryDocumentDB,
49
+ ScriptScheduleRepositoryMongo,
50
+ ScriptScheduleRepositoryPostgres,
51
+ ScriptScheduleRepositoryDocumentDB,
52
+ };
@@ -0,0 +1,21 @@
1
+ const {
2
+ AdminProcessRepositoryMongo,
3
+ } = require('./admin-process-repository-mongo');
4
+
5
+ /**
6
+ * DocumentDB Admin Process Repository Adapter
7
+ * Extends MongoDB implementation since DocumentDB uses the same Prisma client
8
+ *
9
+ * DocumentDB-specific characteristics:
10
+ * - Uses MongoDB-compatible API
11
+ * - Prisma client handles the connection
12
+ * - IDs are strings with ObjectId format
13
+ * - All operations identical to MongoDB implementation
14
+ */
15
+ class AdminProcessRepositoryDocumentDB extends AdminProcessRepositoryMongo {
16
+ constructor() {
17
+ super();
18
+ }
19
+ }
20
+
21
+ module.exports = { AdminProcessRepositoryDocumentDB };
@@ -0,0 +1,51 @@
1
+ const { AdminProcessRepositoryMongo } = require('./admin-process-repository-mongo');
2
+ const { AdminProcessRepositoryPostgres } = require('./admin-process-repository-postgres');
3
+ const {
4
+ AdminProcessRepositoryDocumentDB,
5
+ } = require('./admin-process-repository-documentdb');
6
+ const config = require('../../database/config');
7
+
8
+ /**
9
+ * Admin Process Repository Factory
10
+ * Creates the appropriate repository adapter based on database type
11
+ *
12
+ * This implements the Factory pattern for Hexagonal Architecture:
13
+ * - Reads database type from app definition (backend/index.js)
14
+ * - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
15
+ * - Provides clear error for unsupported databases
16
+ *
17
+ * Usage:
18
+ * ```javascript
19
+ * const repository = createAdminProcessRepository();
20
+ * ```
21
+ *
22
+ * @returns {AdminProcessRepositoryInterface} Configured repository adapter
23
+ * @throws {Error} If database type is not supported
24
+ */
25
+ function createAdminProcessRepository() {
26
+ const dbType = config.DB_TYPE;
27
+
28
+ switch (dbType) {
29
+ case 'mongodb':
30
+ return new AdminProcessRepositoryMongo();
31
+
32
+ case 'postgresql':
33
+ return new AdminProcessRepositoryPostgres();
34
+
35
+ case 'documentdb':
36
+ return new AdminProcessRepositoryDocumentDB();
37
+
38
+ default:
39
+ throw new Error(
40
+ `Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
41
+ );
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ createAdminProcessRepository,
47
+ // Export adapters for direct testing
48
+ AdminProcessRepositoryMongo,
49
+ AdminProcessRepositoryPostgres,
50
+ AdminProcessRepositoryDocumentDB,
51
+ };
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Admin Process Repository Interface
3
+ * Abstract base class defining the contract for admin process persistence adapters
4
+ *
5
+ * This follows the Port in Hexagonal Architecture:
6
+ * - Domain layer depends on this abstraction
7
+ * - Concrete adapters implement this interface
8
+ * - Use cases receive repositories via dependency injection
9
+ *
10
+ * Admin processes track administrative operations including:
11
+ * - Admin script executions
12
+ * - Database migrations
13
+ * - Scheduled maintenance tasks
14
+ *
15
+ * The AdminProcess model uses a flexible JSON storage pattern:
16
+ * - context: Input parameters, trigger info, audit data, script version
17
+ * - results: Output data, logs, metrics, error details
18
+ *
19
+ * @abstract
20
+ */
21
+ class AdminProcessRepositoryInterface {
22
+ /**
23
+ * Create a new admin process record
24
+ *
25
+ * @param {Object} params - Process creation parameters
26
+ * @param {string} params.name - Name of the process (e.g., script name, migration name)
27
+ * @param {string} params.type - Type of process (e.g., 'ADMIN_SCRIPT', 'DB_MIGRATION')
28
+ * @param {Object} [params.context] - Context data (input, trigger, audit, script version)
29
+ * @param {string} [params.context.scriptVersion] - Version of the script
30
+ * @param {string} [params.context.trigger] - Trigger type ('MANUAL', 'SCHEDULED', 'QUEUE', 'WEBHOOK')
31
+ * @param {string} [params.context.mode] - Execution mode ('sync' or 'async')
32
+ * @param {Object} [params.context.input] - Input parameters
33
+ * @param {Object} [params.context.audit] - Audit information
34
+ * @param {string} [params.context.audit.apiKeyName] - Name of API key used
35
+ * @param {string} [params.context.audit.apiKeyLast4] - Last 4 chars of API key
36
+ * @param {string} [params.context.audit.ipAddress] - IP address of requester
37
+ * @returns {Promise<Object>} The created process record
38
+ * @abstract
39
+ */
40
+ async createProcess({ name, type, context }) {
41
+ throw new Error('Method createProcess must be implemented by subclass');
42
+ }
43
+
44
+ /**
45
+ * Find a process by its ID
46
+ *
47
+ * @param {string|number} id - The process ID
48
+ * @returns {Promise<Object|null>} The process record or null if not found
49
+ * @abstract
50
+ */
51
+ async findProcessById(id) {
52
+ throw new Error(
53
+ 'Method findProcessById must be implemented by subclass'
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Find all processes with a specific name
59
+ *
60
+ * @param {string} name - The process name to filter by
61
+ * @param {Object} [options] - Query options
62
+ * @param {number} [options.limit] - Maximum number of results
63
+ * @param {number} [options.offset] - Number of results to skip
64
+ * @param {string} [options.sortBy] - Field to sort by
65
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
66
+ * @param {string} [options.state] - Optional state filter ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
67
+ * @returns {Promise<Array>} Array of process records
68
+ * @abstract
69
+ */
70
+ async findProcessesByName(name, options = {}) {
71
+ throw new Error(
72
+ 'Method findProcessesByName must be implemented by subclass'
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Find all processes with a specific state
78
+ *
79
+ * @param {string} state - State to filter by ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
80
+ * @param {Object} [options] - Query options
81
+ * @param {number} [options.limit] - Maximum number of results
82
+ * @param {number} [options.offset] - Number of results to skip
83
+ * @param {string} [options.sortBy] - Field to sort by
84
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
85
+ * @returns {Promise<Array>} Array of process records
86
+ * @abstract
87
+ */
88
+ async findProcessesByState(state, options = {}) {
89
+ throw new Error(
90
+ 'Method findProcessesByState must be implemented by subclass'
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Update the state of a process
96
+ *
97
+ * @param {string|number} id - The process ID
98
+ * @param {string} state - New state value ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
99
+ * @returns {Promise<Object>} Updated process record
100
+ * @abstract
101
+ */
102
+ async updateProcessState(id, state) {
103
+ throw new Error(
104
+ 'Method updateProcessState must be implemented by subclass'
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Update the results of a process
110
+ * Merges new results with existing results in the results JSON field
111
+ *
112
+ * @param {string|number} id - The process ID
113
+ * @param {Object} results - Results data to merge
114
+ * @param {Object} [results.output] - Output data from the process
115
+ * @param {Object} [results.error] - Error information
116
+ * @param {string} [results.error.name] - Error name/type
117
+ * @param {string} [results.error.message] - Error message
118
+ * @param {string} [results.error.stack] - Error stack trace
119
+ * @param {Object} [results.metrics] - Performance metrics
120
+ * @param {Date} [results.metrics.startTime] - Process start time
121
+ * @param {Date} [results.metrics.endTime] - Process end time
122
+ * @param {number} [results.metrics.durationMs] - Duration in milliseconds
123
+ * @returns {Promise<Object>} Updated process record
124
+ * @abstract
125
+ */
126
+ async updateProcessResults(id, results) {
127
+ throw new Error(
128
+ 'Method updateProcessResults must be implemented by subclass'
129
+ );
130
+ }
131
+
132
+ /**
133
+ * Append a log entry to a process's log array in results
134
+ *
135
+ * @param {string|number} id - The process ID
136
+ * @param {Object} logEntry - Log entry to append
137
+ * @param {string} logEntry.level - Log level ('debug', 'info', 'warn', 'error')
138
+ * @param {string} logEntry.message - Log message
139
+ * @param {Object} [logEntry.data] - Additional log data
140
+ * @param {string} logEntry.timestamp - ISO timestamp
141
+ * @returns {Promise<Object>} Updated process record
142
+ * @abstract
143
+ */
144
+ async appendProcessLog(id, logEntry) {
145
+ throw new Error(
146
+ 'Method appendProcessLog must be implemented by subclass'
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Delete all processes older than a specific date
152
+ * Used for cleanup and retention policies
153
+ *
154
+ * @param {Date} date - Delete processes older than this date
155
+ * @returns {Promise<Object>} Deletion result with count
156
+ * @abstract
157
+ */
158
+ async deleteProcessesOlderThan(date) {
159
+ throw new Error(
160
+ 'Method deleteProcessesOlderThan must be implemented by subclass'
161
+ );
162
+ }
163
+ }
164
+
165
+ module.exports = { AdminProcessRepositoryInterface };
@@ -0,0 +1,227 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AdminProcessRepositoryInterface,
4
+ } = require('./admin-process-repository-interface');
5
+
6
+ /**
7
+ * MongoDB Admin Process Repository Adapter
8
+ * Handles admin process persistence using Prisma with MongoDB
9
+ *
10
+ * MongoDB-specific characteristics:
11
+ * - IDs are strings with @db.ObjectId
12
+ * - context and results are Json objects
13
+ * - Stores logs in results.logs array
14
+ */
15
+ class AdminProcessRepositoryMongo extends AdminProcessRepositoryInterface {
16
+ constructor() {
17
+ super();
18
+ this.prisma = prisma;
19
+ }
20
+
21
+ /**
22
+ * Create a new admin process record
23
+ *
24
+ * @param {Object} params - Process creation parameters
25
+ * @param {string} params.name - Name of the process
26
+ * @param {string} params.type - Type of process (e.g., 'ADMIN_SCRIPT', 'DB_MIGRATION')
27
+ * @param {Object} [params.context] - Context data
28
+ * @returns {Promise<Object>} The created process record
29
+ */
30
+ async createProcess({ name, type, context = {} }) {
31
+ const data = {
32
+ name,
33
+ type,
34
+ context,
35
+ results: { logs: [] },
36
+ };
37
+
38
+ const process = await this.prisma.adminProcess.create({
39
+ data,
40
+ });
41
+
42
+ return process;
43
+ }
44
+
45
+ /**
46
+ * Find a process by its ID
47
+ *
48
+ * @param {string} id - The process ID
49
+ * @returns {Promise<Object|null>} The process record or null if not found
50
+ */
51
+ async findProcessById(id) {
52
+ const process = await this.prisma.adminProcess.findUnique({
53
+ where: { id },
54
+ });
55
+
56
+ return process;
57
+ }
58
+
59
+ /**
60
+ * Find all processes with a specific name
61
+ *
62
+ * @param {string} name - The process name to filter by
63
+ * @param {Object} [options] - Query options
64
+ * @param {number} [options.limit] - Maximum number of results
65
+ * @param {number} [options.offset] - Number of results to skip
66
+ * @param {string} [options.sortBy] - Field to sort by
67
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
68
+ * @returns {Promise<Array>} Array of process records
69
+ */
70
+ async findProcessesByName(name, options = {}) {
71
+ const {
72
+ limit,
73
+ offset,
74
+ sortBy = 'createdAt',
75
+ sortOrder = 'desc',
76
+ state,
77
+ } = options;
78
+
79
+ const where = { name };
80
+ if (state) where.state = state;
81
+
82
+ const processes = await this.prisma.adminProcess.findMany({
83
+ where,
84
+ orderBy: { [sortBy]: sortOrder },
85
+ take: limit,
86
+ skip: offset,
87
+ });
88
+
89
+ return processes;
90
+ }
91
+
92
+ /**
93
+ * Find all processes with a specific state
94
+ *
95
+ * @param {string} state - State to filter by
96
+ * @param {Object} [options] - Query options
97
+ * @param {number} [options.limit] - Maximum number of results
98
+ * @param {number} [options.offset] - Number of results to skip
99
+ * @param {string} [options.sortBy] - Field to sort by
100
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
101
+ * @returns {Promise<Array>} Array of process records
102
+ */
103
+ async findProcessesByState(state, options = {}) {
104
+ const {
105
+ limit,
106
+ offset,
107
+ sortBy = 'createdAt',
108
+ sortOrder = 'desc',
109
+ } = options;
110
+
111
+ const processes = await this.prisma.adminProcess.findMany({
112
+ where: { state },
113
+ orderBy: { [sortBy]: sortOrder },
114
+ take: limit,
115
+ skip: offset,
116
+ });
117
+
118
+ return processes;
119
+ }
120
+
121
+ /**
122
+ * Update the state of a process
123
+ *
124
+ * @param {string} id - The process ID
125
+ * @param {string} state - New state value
126
+ * @returns {Promise<Object>} Updated process record
127
+ */
128
+ async updateProcessState(id, state) {
129
+ const process = await this.prisma.adminProcess.update({
130
+ where: { id },
131
+ data: { state },
132
+ });
133
+
134
+ return process;
135
+ }
136
+
137
+ /**
138
+ * Update the results of a process
139
+ * Merges new results with existing results
140
+ *
141
+ * @param {string} id - The process ID
142
+ * @param {Object} results - Results data to merge
143
+ * @returns {Promise<Object>} Updated process record
144
+ */
145
+ async updateProcessResults(id, results) {
146
+ // Get current process to merge results
147
+ const currentProcess = await this.prisma.adminProcess.findUnique({
148
+ where: { id },
149
+ });
150
+
151
+ if (!currentProcess) {
152
+ throw new Error(`AdminProcess ${id} not found`);
153
+ }
154
+
155
+ // Merge new results with existing results
156
+ const mergedResults = {
157
+ ...(currentProcess.results || {}),
158
+ ...results,
159
+ };
160
+
161
+ const process = await this.prisma.adminProcess.update({
162
+ where: { id },
163
+ data: { results: mergedResults },
164
+ });
165
+
166
+ return process;
167
+ }
168
+
169
+ /**
170
+ * Append a log entry to a process's log array in results
171
+ *
172
+ * @param {string} id - The process ID
173
+ * @param {Object} logEntry - Log entry to append
174
+ * @param {string} logEntry.level - Log level ('debug', 'info', 'warn', 'error')
175
+ * @param {string} logEntry.message - Log message
176
+ * @param {Object} [logEntry.data] - Additional log data
177
+ * @param {string} logEntry.timestamp - ISO timestamp
178
+ * @returns {Promise<Object>} Updated process record
179
+ */
180
+ async appendProcessLog(id, logEntry) {
181
+ // Get current process
182
+ const process = await this.prisma.adminProcess.findUnique({
183
+ where: { id },
184
+ });
185
+
186
+ if (!process) {
187
+ throw new Error(`AdminProcess ${id} not found`);
188
+ }
189
+
190
+ // Get current results and logs
191
+ const results = process.results || {};
192
+ const logs = Array.isArray(results.logs) ? [...results.logs] : [];
193
+ logs.push(logEntry);
194
+
195
+ // Update with new logs array in results
196
+ const updated = await this.prisma.adminProcess.update({
197
+ where: { id },
198
+ data: { results: { ...results, logs } },
199
+ });
200
+
201
+ return updated;
202
+ }
203
+
204
+ /**
205
+ * Delete all processes older than a specific date
206
+ * Used for cleanup and retention policies
207
+ *
208
+ * @param {Date} date - Delete processes older than this date
209
+ * @returns {Promise<Object>} Deletion result with count
210
+ */
211
+ async deleteProcessesOlderThan(date) {
212
+ const result = await this.prisma.adminProcess.deleteMany({
213
+ where: {
214
+ createdAt: {
215
+ lt: date,
216
+ },
217
+ },
218
+ });
219
+
220
+ return {
221
+ acknowledged: true,
222
+ deletedCount: result.count,
223
+ };
224
+ }
225
+ }
226
+
227
+ module.exports = { AdminProcessRepositoryMongo };