@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 (237) hide show
  1. package/CHANGELOG.md +66 -67
  2. package/CLAUDE.md +148 -124
  3. package/README.md +165 -130
  4. package/admin-scripts/index.js +52 -0
  5. package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
  6. package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
  7. package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
  8. package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
  9. package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
  10. package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
  11. package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
  12. package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
  13. package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
  14. package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
  15. package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
  16. package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
  17. package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
  18. package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
  19. package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
  20. package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
  21. package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
  22. package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
  23. package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
  24. package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
  25. package/application/commands/admin-script-commands.js +348 -0
  26. package/application/commands/integration-commands.js +12 -6
  27. package/application/index.js +5 -3
  28. package/assertions/get.js +17 -7
  29. package/associations/association.js +59 -57
  30. package/core/CLAUDE.md +222 -156
  31. package/core/Worker.js +33 -12
  32. package/credential/credential-router.js +262 -0
  33. package/credential/repositories/credential-repository-documentdb.js +71 -1
  34. package/credential/repositories/credential-repository-factory.js +4 -6
  35. package/credential/repositories/credential-repository-interface.js +8 -2
  36. package/credential/repositories/credential-repository-mongo.js +45 -4
  37. package/credential/repositories/credential-repository-postgres.js +44 -3
  38. package/credential/repositories/credential-repository.js +3 -1
  39. package/credential/use-cases/delete-credential-for-user.js +50 -0
  40. package/credential/use-cases/get-credential-for-user.js +21 -3
  41. package/credential/use-cases/list-credentials-for-user.js +31 -0
  42. package/credential/use-cases/reauthorize-credential.js +103 -0
  43. package/credential/use-cases/update-authentication-status.js +5 -2
  44. package/database/MONGODB_TRANSACTION_FIX.md +72 -52
  45. package/database/config.js +13 -59
  46. package/database/documentdb-encryption-service.js +29 -13
  47. package/database/documentdb-utils.js +21 -12
  48. package/database/encryption/README.md +53 -36
  49. package/database/encryption/encryption-schema-registry.js +29 -23
  50. package/database/encryption/field-encryption-service.js +15 -4
  51. package/database/encryption/logger.js +3 -2
  52. package/database/encryption/prisma-encryption-extension.js +5 -8
  53. package/database/prisma.js +22 -14
  54. package/database/repositories/health-check-repository-factory.js +7 -5
  55. package/database/repositories/health-check-repository-interface.js +15 -5
  56. package/database/repositories/health-check-repository-postgres.js +1 -1
  57. package/database/use-cases/check-database-health-use-case.js +3 -2
  58. package/database/use-cases/check-database-state-use-case.js +8 -7
  59. package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
  60. package/database/use-cases/get-migration-status-use-case.js +4 -2
  61. package/database/use-cases/run-database-migration-use-case.js +41 -9
  62. package/database/use-cases/test-encryption-use-case.js +4 -5
  63. package/database/use-cases/trigger-database-migration-use-case.js +9 -8
  64. package/database/utils/mongodb-collection-utils.js +11 -3
  65. package/database/utils/mongodb-schema-init.js +10 -4
  66. package/database/utils/prisma-runner.js +144 -74
  67. package/database/utils/prisma-schema-parser.js +2 -2
  68. package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
  69. package/encrypt/Cryptor.js +54 -49
  70. package/encrypt/aes-encryption-key-provider.js +82 -0
  71. package/encrypt/encryption-key-provider-interface.js +50 -0
  72. package/errors/fetch-error.js +1 -1
  73. package/generated/prisma-mongodb/edge.js +59 -4
  74. package/generated/prisma-mongodb/index-browser.js +49 -1
  75. package/generated/prisma-mongodb/index.d.ts +4023 -61
  76. package/generated/prisma-mongodb/index.js +59 -4
  77. package/generated/prisma-mongodb/package.json +1 -1
  78. package/generated/prisma-mongodb/schema.prisma +87 -0
  79. package/generated/prisma-mongodb/wasm.js +59 -4
  80. package/generated/prisma-postgresql/edge.js +58 -4
  81. package/generated/prisma-postgresql/index-browser.js +48 -1
  82. package/generated/prisma-postgresql/index.d.ts +4738 -54
  83. package/generated/prisma-postgresql/index.js +58 -4
  84. package/generated/prisma-postgresql/package.json +1 -1
  85. package/generated/prisma-postgresql/schema.prisma +85 -0
  86. package/generated/prisma-postgresql/wasm.js +58 -4
  87. package/handlers/WEBHOOKS.md +104 -81
  88. package/handlers/app-definition-loader.js +53 -10
  89. package/handlers/app-handler-helpers.js +14 -3
  90. package/handlers/backend-utils.js +0 -1
  91. package/handlers/database-migration-handler.js +64 -45
  92. package/handlers/middleware/admin-auth.js +53 -0
  93. package/handlers/routers/HEALTHCHECK.md +170 -135
  94. package/handlers/routers/admin.js +474 -0
  95. package/handlers/routers/auth.js +51 -9
  96. package/handlers/routers/db-migration.handler.js +2 -3
  97. package/handlers/routers/db-migration.js +102 -66
  98. package/handlers/routers/docs.js +180 -0
  99. package/handlers/routers/health.js +84 -283
  100. package/handlers/routers/integration-defined-routers.js +44 -33
  101. package/handlers/routers/integration-webhook-routers.js +73 -52
  102. package/handlers/routers/middleware/loadUser.js +39 -0
  103. package/handlers/routers/middleware/requireAdmin.js +43 -0
  104. package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
  105. package/handlers/routers/user.js +30 -14
  106. package/handlers/routers/websocket.js +18 -4
  107. package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
  108. package/handlers/workers/db-migration.js +74 -35
  109. package/handlers/workers/integration-defined-workers.js +31 -20
  110. package/index.js +50 -16
  111. package/infrastructure/scheduler/index.js +15 -11
  112. package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
  113. package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
  114. package/integrations/WEBHOOK-QUICKSTART.md +21 -14
  115. package/integrations/index.js +4 -0
  116. package/integrations/integration-base.js +9 -6
  117. package/integrations/integration-router.js +1828 -157
  118. package/integrations/options.js +47 -20
  119. package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
  120. package/integrations/repositories/integration-repository-documentdb.js +53 -24
  121. package/integrations/repositories/integration-repository-factory.js +6 -6
  122. package/integrations/repositories/integration-repository-interface.js +27 -9
  123. package/integrations/repositories/integration-repository-postgres.js +3 -3
  124. package/integrations/repositories/process-repository-factory.js +4 -7
  125. package/integrations/repositories/process-repository-interface.js +9 -5
  126. package/integrations/repositories/process-repository-mongo.js +7 -3
  127. package/integrations/repositories/process-repository-postgres.js +2 -2
  128. package/integrations/tests/doubles/config-capturing-integration.js +9 -8
  129. package/integrations/tests/doubles/dummy-integration-class.js +81 -8
  130. package/integrations/tests/doubles/test-integration-repository.js +51 -11
  131. package/integrations/use-cases/create-integration.js +36 -32
  132. package/integrations/use-cases/create-process.js +20 -9
  133. package/integrations/use-cases/delete-integration-for-user.js +5 -4
  134. package/integrations/use-cases/execute-proxy-request.js +564 -0
  135. package/integrations/use-cases/get-integration-for-user.js +25 -10
  136. package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
  137. package/integrations/use-cases/get-integrations-for-user.js +4 -1
  138. package/integrations/use-cases/get-possible-integrations.js +1 -1
  139. package/integrations/use-cases/get-process.js +5 -6
  140. package/integrations/use-cases/index.js +3 -1
  141. package/integrations/use-cases/load-integration-context.js +1 -5
  142. package/integrations/use-cases/update-process-state.js +10 -8
  143. package/integrations/utils/map-integration-dto.js +9 -3
  144. package/jest-setup.js +1 -1
  145. package/jest-teardown.js +1 -1
  146. package/lambda/TimeoutCatcher.js +32 -30
  147. package/lambda/index.js +1 -1
  148. package/logs/index.js +3 -3
  149. package/modules/domain/entities/AuthorizationSession.js +131 -0
  150. package/modules/domain/entities/index.js +10 -0
  151. package/modules/module.js +4 -9
  152. package/modules/repositories/authorization-session-repository-factory.js +53 -0
  153. package/modules/repositories/authorization-session-repository-interface.js +75 -0
  154. package/modules/repositories/authorization-session-repository-mongo.js +169 -0
  155. package/modules/repositories/authorization-session-repository-postgres.js +170 -0
  156. package/modules/repositories/module-repository-documentdb.js +89 -41
  157. package/modules/repositories/module-repository-factory.js +4 -6
  158. package/modules/repositories/module-repository-interface.js +13 -0
  159. package/modules/repositories/module-repository-mongo.js +40 -5
  160. package/modules/repositories/module-repository-postgres.js +42 -5
  161. package/modules/repositories/module-repository.js +28 -0
  162. package/modules/requester/api-key.js +0 -2
  163. package/modules/requester/basic.js +0 -1
  164. package/modules/requester/oauth-2.js +3 -14
  165. package/modules/test/mock-api/definition.js +15 -10
  166. package/modules/test/mock-api/mocks/hubspot.js +36 -36
  167. package/modules/tests/doubles/test-module-factory.js +10 -4
  168. package/modules/tests/doubles/test-module-repository.js +1 -1
  169. package/modules/use-cases/delete-module-entity.js +23 -0
  170. package/modules/use-cases/get-authorization-requirements.js +110 -0
  171. package/modules/use-cases/get-entities-for-user.js +1 -1
  172. package/modules/use-cases/get-entity-options-by-id.js +8 -6
  173. package/modules/use-cases/get-module-entity-by-id.js +16 -0
  174. package/modules/use-cases/get-module.js +12 -9
  175. package/modules/use-cases/process-authorization-callback.js +12 -6
  176. package/modules/use-cases/process-authorization-step.js +160 -0
  177. package/modules/use-cases/refresh-entity-options.js +8 -6
  178. package/modules/use-cases/start-authorization-session.js +86 -0
  179. package/modules/use-cases/test-module-auth.js +8 -6
  180. package/modules/use-cases/update-module-entity.js +27 -0
  181. package/modules/utils/map-module-dto.js +2 -2
  182. package/openapi/openapi-spec-generator.js +370 -0
  183. package/openapi/openapi-v1.yaml +1360 -0
  184. package/openapi/openapi-v2.yaml +1535 -0
  185. package/package.json +7 -9
  186. package/prisma-mongodb/schema.prisma +87 -0
  187. package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
  188. package/prisma-postgresql/schema.prisma +85 -0
  189. package/providers/index.js +13 -0
  190. package/providers/resolve-provider.js +93 -0
  191. package/queues/index.js +19 -0
  192. package/queues/providers/index.js +7 -0
  193. package/queues/providers/netlify-background-provider.js +76 -0
  194. package/queues/providers/qstash-queue-provider.js +122 -0
  195. package/queues/queue-client-interface.js +55 -0
  196. package/queues/queue-provider-factory.js +93 -0
  197. package/queues/queue-provider.js +46 -0
  198. package/queues/queuer-util.js +33 -25
  199. package/syncs/repositories/sync-repository-documentdb.js +39 -19
  200. package/syncs/repositories/sync-repository-factory.js +4 -4
  201. package/syncs/repositories/sync-repository-interface.js +6 -2
  202. package/syncs/repositories/sync-repository-postgres.js +19 -13
  203. package/syncs/sync.js +104 -100
  204. package/token/repositories/token-repository-factory.js +4 -6
  205. package/types/assertions/index.d.ts +72 -72
  206. package/types/core/index.d.ts +98 -53
  207. package/types/errors/index.d.ts +61 -54
  208. package/types/eslint-config/index.d.ts +40 -40
  209. package/types/lambda/index.d.ts +26 -26
  210. package/types/logs/index.d.ts +4 -4
  211. package/types/prettier-config/index.d.ts +5 -5
  212. package/types/test-environment/index.d.ts +14 -14
  213. package/types/tsconfig.json +95 -93
  214. package/user/repositories/user-repository-factory.js +4 -4
  215. package/user/tests/doubles/test-user-repository.js +5 -2
  216. package/user/use-cases/authenticate-user.js +2 -4
  217. package/user/use-cases/authenticate-with-shared-secret.js +1 -1
  218. package/user/use-cases/create-individual-user.js +8 -7
  219. package/user/use-cases/create-organization-user.js +1 -1
  220. package/user/use-cases/create-token-for-user-id.js +1 -1
  221. package/user/use-cases/delete-user.js +47 -0
  222. package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
  223. package/user/use-cases/get-user-from-bearer-token.js +7 -3
  224. package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
  225. package/user/use-cases/login-user.js +8 -8
  226. package/user/user.js +18 -4
  227. package/utils/backend-path.js +2 -2
  228. package/utils/index.js +4 -1
  229. package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
  230. package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
  231. package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
  232. package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
  233. package/websocket/repositories/websocket-connection-repository.js +26 -20
  234. package/websocket/websocket-message-sender-interface.js +38 -0
  235. package/database/adapters/lambda-invoker.js +0 -97
  236. package/database/repositories/migration-status-repository-s3.js +0 -137
  237. package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
@@ -0,0 +1,150 @@
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('Method findProcessById must be implemented by subclass');
53
+ }
54
+
55
+ /**
56
+ * Find all processes with a specific name
57
+ *
58
+ * @param {string} name - The process name to filter by
59
+ * @param {Object} [options] - Query options
60
+ * @param {number} [options.limit] - Maximum number of results
61
+ * @param {number} [options.offset] - Number of results to skip
62
+ * @param {string} [options.sortBy] - Field to sort by
63
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
64
+ * @returns {Promise<Array>} Array of process records
65
+ * @abstract
66
+ */
67
+ async findProcessesByName(name, options = {}) {
68
+ throw new Error('Method findProcessesByName must be implemented by subclass');
69
+ }
70
+
71
+ /**
72
+ * Find all processes with a specific state
73
+ *
74
+ * @param {string} state - State to filter by ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
75
+ * @param {Object} [options] - Query options
76
+ * @param {number} [options.limit] - Maximum number of results
77
+ * @param {number} [options.offset] - Number of results to skip
78
+ * @param {string} [options.sortBy] - Field to sort by
79
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
80
+ * @returns {Promise<Array>} Array of process records
81
+ * @abstract
82
+ */
83
+ async findProcessesByState(state, options = {}) {
84
+ throw new Error('Method findProcessesByState must be implemented by subclass');
85
+ }
86
+
87
+ /**
88
+ * Update the state of a process
89
+ *
90
+ * @param {string|number} id - The process ID
91
+ * @param {string} state - New state value ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
92
+ * @returns {Promise<Object>} Updated process record
93
+ * @abstract
94
+ */
95
+ async updateProcessState(id, state) {
96
+ throw new Error('Method updateProcessState must be implemented by subclass');
97
+ }
98
+
99
+ /**
100
+ * Update the results of a process
101
+ * Merges new results with existing results in the results JSON field
102
+ *
103
+ * @param {string|number} id - The process ID
104
+ * @param {Object} results - Results data to merge
105
+ * @param {Object} [results.output] - Output data from the process
106
+ * @param {Object} [results.error] - Error information
107
+ * @param {string} [results.error.name] - Error name/type
108
+ * @param {string} [results.error.message] - Error message
109
+ * @param {string} [results.error.stack] - Error stack trace
110
+ * @param {Object} [results.metrics] - Performance metrics
111
+ * @param {Date} [results.metrics.startTime] - Process start time
112
+ * @param {Date} [results.metrics.endTime] - Process end time
113
+ * @param {number} [results.metrics.durationMs] - Duration in milliseconds
114
+ * @returns {Promise<Object>} Updated process record
115
+ * @abstract
116
+ */
117
+ async updateProcessResults(id, results) {
118
+ throw new Error('Method updateProcessResults must be implemented by subclass');
119
+ }
120
+
121
+ /**
122
+ * Append a log entry to a process's log array in results
123
+ *
124
+ * @param {string|number} id - The process ID
125
+ * @param {Object} logEntry - Log entry to append
126
+ * @param {string} logEntry.level - Log level ('debug', 'info', 'warn', 'error')
127
+ * @param {string} logEntry.message - Log message
128
+ * @param {Object} [logEntry.data] - Additional log data
129
+ * @param {string} logEntry.timestamp - ISO timestamp
130
+ * @returns {Promise<Object>} Updated process record
131
+ * @abstract
132
+ */
133
+ async appendProcessLog(id, logEntry) {
134
+ throw new Error('Method appendProcessLog must be implemented by subclass');
135
+ }
136
+
137
+ /**
138
+ * Delete all processes older than a specific date
139
+ * Used for cleanup and retention policies
140
+ *
141
+ * @param {Date} date - Delete processes older than this date
142
+ * @returns {Promise<Object>} Deletion result with count
143
+ * @abstract
144
+ */
145
+ async deleteProcessesOlderThan(date) {
146
+ throw new Error('Method deleteProcessesOlderThan must be implemented by subclass');
147
+ }
148
+ }
149
+
150
+ module.exports = { AdminProcessRepositoryInterface };
@@ -0,0 +1,213 @@
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 { limit, offset, sortBy = 'createdAt', sortOrder = 'desc' } = options;
72
+
73
+ const processes = await this.prisma.adminProcess.findMany({
74
+ where: { name },
75
+ orderBy: { [sortBy]: sortOrder },
76
+ take: limit,
77
+ skip: offset,
78
+ });
79
+
80
+ return processes;
81
+ }
82
+
83
+ /**
84
+ * Find all processes with a specific state
85
+ *
86
+ * @param {string} state - State to filter by
87
+ * @param {Object} [options] - Query options
88
+ * @param {number} [options.limit] - Maximum number of results
89
+ * @param {number} [options.offset] - Number of results to skip
90
+ * @param {string} [options.sortBy] - Field to sort by
91
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
92
+ * @returns {Promise<Array>} Array of process records
93
+ */
94
+ async findProcessesByState(state, options = {}) {
95
+ const { limit, offset, sortBy = 'createdAt', sortOrder = 'desc' } = options;
96
+
97
+ const processes = await this.prisma.adminProcess.findMany({
98
+ where: { state },
99
+ orderBy: { [sortBy]: sortOrder },
100
+ take: limit,
101
+ skip: offset,
102
+ });
103
+
104
+ return processes;
105
+ }
106
+
107
+ /**
108
+ * Update the state of a process
109
+ *
110
+ * @param {string} id - The process ID
111
+ * @param {string} state - New state value
112
+ * @returns {Promise<Object>} Updated process record
113
+ */
114
+ async updateProcessState(id, state) {
115
+ const process = await this.prisma.adminProcess.update({
116
+ where: { id },
117
+ data: { state },
118
+ });
119
+
120
+ return process;
121
+ }
122
+
123
+ /**
124
+ * Update the results of a process
125
+ * Merges new results with existing results
126
+ *
127
+ * @param {string} id - The process ID
128
+ * @param {Object} results - Results data to merge
129
+ * @returns {Promise<Object>} Updated process record
130
+ */
131
+ async updateProcessResults(id, results) {
132
+ // Get current process to merge results
133
+ const currentProcess = await this.prisma.adminProcess.findUnique({
134
+ where: { id },
135
+ });
136
+
137
+ if (!currentProcess) {
138
+ throw new Error(`AdminProcess ${id} not found`);
139
+ }
140
+
141
+ // Merge new results with existing results
142
+ const mergedResults = {
143
+ ...(currentProcess.results || {}),
144
+ ...results,
145
+ };
146
+
147
+ const process = await this.prisma.adminProcess.update({
148
+ where: { id },
149
+ data: { results: mergedResults },
150
+ });
151
+
152
+ return process;
153
+ }
154
+
155
+ /**
156
+ * Append a log entry to a process's log array in results
157
+ *
158
+ * @param {string} id - The process ID
159
+ * @param {Object} logEntry - Log entry to append
160
+ * @param {string} logEntry.level - Log level ('debug', 'info', 'warn', 'error')
161
+ * @param {string} logEntry.message - Log message
162
+ * @param {Object} [logEntry.data] - Additional log data
163
+ * @param {string} logEntry.timestamp - ISO timestamp
164
+ * @returns {Promise<Object>} Updated process record
165
+ */
166
+ async appendProcessLog(id, logEntry) {
167
+ // Get current process
168
+ const process = await this.prisma.adminProcess.findUnique({
169
+ where: { id },
170
+ });
171
+
172
+ if (!process) {
173
+ throw new Error(`AdminProcess ${id} not found`);
174
+ }
175
+
176
+ // Get current results and logs
177
+ const results = process.results || {};
178
+ const logs = Array.isArray(results.logs) ? [...results.logs] : [];
179
+ logs.push(logEntry);
180
+
181
+ // Update with new logs array in results
182
+ const updated = await this.prisma.adminProcess.update({
183
+ where: { id },
184
+ data: { results: { ...results, logs } },
185
+ });
186
+
187
+ return updated;
188
+ }
189
+
190
+ /**
191
+ * Delete all processes older than a specific date
192
+ * Used for cleanup and retention policies
193
+ *
194
+ * @param {Date} date - Delete processes older than this date
195
+ * @returns {Promise<Object>} Deletion result with count
196
+ */
197
+ async deleteProcessesOlderThan(date) {
198
+ const result = await this.prisma.adminProcess.deleteMany({
199
+ where: {
200
+ createdAt: {
201
+ lt: date,
202
+ },
203
+ },
204
+ });
205
+
206
+ return {
207
+ acknowledged: true,
208
+ deletedCount: result.count,
209
+ };
210
+ }
211
+ }
212
+
213
+ module.exports = { AdminProcessRepositoryMongo };
@@ -0,0 +1,251 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ AdminProcessRepositoryInterface,
4
+ } = require('./admin-process-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Admin Process Repository Adapter
8
+ * Handles admin process persistence using Prisma with PostgreSQL
9
+ *
10
+ * PostgreSQL-specific characteristics:
11
+ * - Uses Int IDs with autoincrement
12
+ * - Requires ID conversion: String (app layer) ↔ Int (database)
13
+ * - All returned IDs are converted to strings for application layer consistency
14
+ * - context and results are Json objects
15
+ */
16
+ class AdminProcessRepositoryPostgres extends AdminProcessRepositoryInterface {
17
+ constructor() {
18
+ super();
19
+ this.prisma = prisma;
20
+ }
21
+
22
+ /**
23
+ * Convert string ID to integer for PostgreSQL queries
24
+ * @private
25
+ * @param {string|number|null|undefined} id - ID to convert
26
+ * @returns {number|null|undefined} Integer ID or null/undefined
27
+ * @throws {Error} If ID cannot be converted to integer
28
+ */
29
+ _convertId(id) {
30
+ if (id === null || id === undefined) return id;
31
+ const parsed = Number.parseInt(id, 10);
32
+ if (Number.isNaN(parsed)) {
33
+ throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
34
+ }
35
+ return parsed;
36
+ }
37
+
38
+ /**
39
+ * Convert process object IDs to strings
40
+ * @private
41
+ * @param {Object|null} process - Process object from database
42
+ * @returns {Object|null} Process with string IDs
43
+ */
44
+ _convertProcessIds(process) {
45
+ if (!process) return process;
46
+ return {
47
+ ...process,
48
+ id: process.id?.toString(),
49
+ parentProcessId: process.parentProcessId?.toString(),
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Create a new admin process record
55
+ *
56
+ * @param {Object} params - Process creation parameters
57
+ * @param {string} params.name - Name of the process
58
+ * @param {string} params.type - Type of process (e.g., 'ADMIN_SCRIPT', 'DB_MIGRATION')
59
+ * @param {Object} [params.context] - Context data
60
+ * @returns {Promise<Object>} The created process record with string ID
61
+ */
62
+ async createProcess({ name, type, context = {} }) {
63
+ const data = {
64
+ name,
65
+ type,
66
+ context,
67
+ results: { logs: [] },
68
+ };
69
+
70
+ const process = await this.prisma.adminProcess.create({
71
+ data,
72
+ });
73
+
74
+ return this._convertProcessIds(process);
75
+ }
76
+
77
+ /**
78
+ * Find a process by its ID
79
+ *
80
+ * @param {string|number} id - The process ID
81
+ * @returns {Promise<Object|null>} The process record with string ID or null if not found
82
+ */
83
+ async findProcessById(id) {
84
+ const intId = this._convertId(id);
85
+ const process = await this.prisma.adminProcess.findUnique({
86
+ where: { id: intId },
87
+ });
88
+
89
+ return this._convertProcessIds(process);
90
+ }
91
+
92
+ /**
93
+ * Find all processes with a specific name
94
+ *
95
+ * @param {string} name - The process name 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 with string IDs
102
+ */
103
+ async findProcessesByName(name, options = {}) {
104
+ const { limit, offset, sortBy = 'createdAt', sortOrder = 'desc' } = options;
105
+
106
+ const processes = await this.prisma.adminProcess.findMany({
107
+ where: { name },
108
+ orderBy: { [sortBy]: sortOrder },
109
+ take: limit,
110
+ skip: offset,
111
+ });
112
+
113
+ return processes.map((process) => this._convertProcessIds(process));
114
+ }
115
+
116
+ /**
117
+ * Find all processes with a specific state
118
+ *
119
+ * @param {string} state - State to filter by
120
+ * @param {Object} [options] - Query options
121
+ * @param {number} [options.limit] - Maximum number of results
122
+ * @param {number} [options.offset] - Number of results to skip
123
+ * @param {string} [options.sortBy] - Field to sort by
124
+ * @param {string} [options.sortOrder] - Sort order ('asc' or 'desc')
125
+ * @returns {Promise<Array>} Array of process records with string IDs
126
+ */
127
+ async findProcessesByState(state, options = {}) {
128
+ const { limit, offset, sortBy = 'createdAt', sortOrder = 'desc' } = options;
129
+
130
+ const processes = await this.prisma.adminProcess.findMany({
131
+ where: { state },
132
+ orderBy: { [sortBy]: sortOrder },
133
+ take: limit,
134
+ skip: offset,
135
+ });
136
+
137
+ return processes.map((process) => this._convertProcessIds(process));
138
+ }
139
+
140
+ /**
141
+ * Update the state of a process
142
+ *
143
+ * @param {string|number} id - The process ID
144
+ * @param {string} state - New state value
145
+ * @returns {Promise<Object>} Updated process record with string ID
146
+ */
147
+ async updateProcessState(id, state) {
148
+ const intId = this._convertId(id);
149
+ const process = await this.prisma.adminProcess.update({
150
+ where: { id: intId },
151
+ data: { state },
152
+ });
153
+
154
+ return this._convertProcessIds(process);
155
+ }
156
+
157
+ /**
158
+ * Update the results of a process
159
+ * Merges new results with existing results
160
+ *
161
+ * @param {string|number} id - The process ID
162
+ * @param {Object} results - Results data to merge
163
+ * @returns {Promise<Object>} Updated process record with string ID
164
+ */
165
+ async updateProcessResults(id, results) {
166
+ const intId = this._convertId(id);
167
+
168
+ // Get current process to merge results
169
+ const currentProcess = await this.prisma.adminProcess.findUnique({
170
+ where: { id: intId },
171
+ });
172
+
173
+ if (!currentProcess) {
174
+ throw new Error(`AdminProcess ${id} not found`);
175
+ }
176
+
177
+ // Merge new results with existing results
178
+ const mergedResults = {
179
+ ...(currentProcess.results || {}),
180
+ ...results,
181
+ };
182
+
183
+ const process = await this.prisma.adminProcess.update({
184
+ where: { id: intId },
185
+ data: { results: mergedResults },
186
+ });
187
+
188
+ return this._convertProcessIds(process);
189
+ }
190
+
191
+ /**
192
+ * Append a log entry to a process's log array in results
193
+ *
194
+ * @param {string|number} id - The process ID
195
+ * @param {Object} logEntry - Log entry to append
196
+ * @param {string} logEntry.level - Log level ('debug', 'info', 'warn', 'error')
197
+ * @param {string} logEntry.message - Log message
198
+ * @param {Object} [logEntry.data] - Additional log data
199
+ * @param {string} logEntry.timestamp - ISO timestamp
200
+ * @returns {Promise<Object>} Updated process record with string ID
201
+ */
202
+ async appendProcessLog(id, logEntry) {
203
+ const intId = this._convertId(id);
204
+
205
+ // Get current process
206
+ const process = await this.prisma.adminProcess.findUnique({
207
+ where: { id: intId },
208
+ });
209
+
210
+ if (!process) {
211
+ throw new Error(`AdminProcess ${id} not found`);
212
+ }
213
+
214
+ // Get current results and logs
215
+ const results = process.results || {};
216
+ const logs = Array.isArray(results.logs) ? [...results.logs] : [];
217
+ logs.push(logEntry);
218
+
219
+ // Update with new logs array in results
220
+ const updated = await this.prisma.adminProcess.update({
221
+ where: { id: intId },
222
+ data: { results: { ...results, logs } },
223
+ });
224
+
225
+ return this._convertProcessIds(updated);
226
+ }
227
+
228
+ /**
229
+ * Delete all processes older than a specific date
230
+ * Used for cleanup and retention policies
231
+ *
232
+ * @param {Date} date - Delete processes older than this date
233
+ * @returns {Promise<Object>} Deletion result with count
234
+ */
235
+ async deleteProcessesOlderThan(date) {
236
+ const result = await this.prisma.adminProcess.deleteMany({
237
+ where: {
238
+ createdAt: {
239
+ lt: date,
240
+ },
241
+ },
242
+ });
243
+
244
+ return {
245
+ acknowledged: true,
246
+ deletedCount: result.count,
247
+ };
248
+ }
249
+ }
250
+
251
+ module.exports = { AdminProcessRepositoryPostgres };
@@ -0,0 +1,21 @@
1
+ const {
2
+ ScriptExecutionRepositoryMongo,
3
+ } = require('./script-execution-repository-mongo');
4
+
5
+ /**
6
+ * DocumentDB Script Execution 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 ScriptExecutionRepositoryDocumentDB extends ScriptExecutionRepositoryMongo {
16
+ constructor() {
17
+ super();
18
+ }
19
+ }
20
+
21
+ module.exports = { ScriptExecutionRepositoryDocumentDB };
@@ -0,0 +1,51 @@
1
+ const {
2
+ ScriptExecutionRepositoryPostgres,
3
+ } = require('./script-execution-repository-postgres');
4
+ const config = require('../../database/config');
5
+
6
+ /**
7
+ * Script Execution Repository Factory
8
+ * Creates the appropriate repository adapter based on database type
9
+ *
10
+ * This implements the Factory pattern for Hexagonal Architecture:
11
+ * - Reads database type from app definition (backend/index.js)
12
+ * - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
13
+ * - Provides clear error for unsupported databases
14
+ *
15
+ * Usage:
16
+ * ```javascript
17
+ * const repository = createScriptExecutionRepository();
18
+ * ```
19
+ *
20
+ * @returns {ScriptExecutionRepositoryInterface} Configured repository adapter
21
+ * @throws {Error} If database type is not supported
22
+ */
23
+ function createScriptExecutionRepository() {
24
+ const dbType = config.DB_TYPE;
25
+
26
+ switch (dbType) {
27
+ case 'mongodb':
28
+ const { ScriptExecutionRepositoryMongo } = require('./script-execution-repository-mongo');
29
+ return new ScriptExecutionRepositoryMongo();
30
+
31
+ case 'postgresql':
32
+ return new ScriptExecutionRepositoryPostgres();
33
+
34
+ case 'documentdb':
35
+ const { ScriptExecutionRepositoryDocumentDB } = require('./script-execution-repository-documentdb');
36
+ return new ScriptExecutionRepositoryDocumentDB();
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
+ createScriptExecutionRepository,
47
+ // Export adapters for direct testing
48
+ get ScriptExecutionRepositoryMongo() { return require('./script-execution-repository-mongo').ScriptExecutionRepositoryMongo; },
49
+ ScriptExecutionRepositoryPostgres,
50
+ get ScriptExecutionRepositoryDocumentDB() { return require('./script-execution-repository-documentdb').ScriptExecutionRepositoryDocumentDB; },
51
+ };