@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,210 @@
1
+ const { prisma } = require('../../database/prisma');
2
+ const {
3
+ ScriptScheduleRepositoryInterface,
4
+ } = require('./script-schedule-repository-interface');
5
+
6
+ /**
7
+ * PostgreSQL Script Schedule Repository Adapter
8
+ * Handles script schedule 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
+ * - scriptName has unique index
15
+ */
16
+ class ScriptScheduleRepositoryPostgres extends ScriptScheduleRepositoryInterface {
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 schedule object IDs to strings
40
+ * @private
41
+ * @param {Object|null} schedule - Schedule object from database
42
+ * @returns {Object|null} Schedule with string IDs
43
+ */
44
+ _convertScheduleIds(schedule) {
45
+ if (!schedule) return schedule;
46
+ return {
47
+ ...schedule,
48
+ id: schedule.id?.toString(),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Find a schedule by script name
54
+ *
55
+ * @param {string} scriptName - The script name
56
+ * @returns {Promise<Object|null>} Schedule record with string ID or null if not found
57
+ */
58
+ async findScheduleByScriptName(scriptName) {
59
+ const schedule = await this.prisma.scriptSchedule.findUnique({
60
+ where: { scriptName },
61
+ });
62
+
63
+ return this._convertScheduleIds(schedule);
64
+ }
65
+
66
+ /**
67
+ * Create or update a schedule (upsert)
68
+ *
69
+ * @param {Object} params - Schedule parameters
70
+ * @param {string} params.scriptName - Name of the script
71
+ * @param {boolean} params.enabled - Whether schedule is enabled
72
+ * @param {string} params.cronExpression - Cron expression
73
+ * @param {string} [params.timezone] - Timezone (default 'UTC')
74
+ * @param {string} [params.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
75
+ * @param {string} [params.externalScheduleName] - External scheduler name
76
+ * @returns {Promise<Object>} Created or updated schedule record with string ID
77
+ */
78
+ async upsertSchedule({ scriptName, enabled, cronExpression, timezone, externalScheduleId, externalScheduleName }) {
79
+ const data = {
80
+ enabled,
81
+ cronExpression,
82
+ timezone: timezone || 'UTC',
83
+ };
84
+
85
+ // Only set external scheduler fields if provided
86
+ if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
87
+ if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
88
+
89
+ const schedule = await this.prisma.scriptSchedule.upsert({
90
+ where: { scriptName },
91
+ update: data,
92
+ create: {
93
+ scriptName,
94
+ ...data,
95
+ },
96
+ });
97
+
98
+ return this._convertScheduleIds(schedule);
99
+ }
100
+
101
+ /**
102
+ * Delete a schedule by script name
103
+ *
104
+ * @param {string} scriptName - The script name
105
+ * @returns {Promise<Object>} Deletion result
106
+ */
107
+ async deleteSchedule(scriptName) {
108
+ try {
109
+ const schedule = await this.prisma.scriptSchedule.delete({
110
+ where: { scriptName },
111
+ });
112
+
113
+ return {
114
+ acknowledged: true,
115
+ deletedCount: 1,
116
+ deleted: this._convertScheduleIds(schedule),
117
+ };
118
+ } catch (error) {
119
+ // Return 0 count if not found
120
+ if (error.code === 'P2025') {
121
+ return {
122
+ acknowledged: true,
123
+ deletedCount: 0,
124
+ };
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Update external scheduler information
132
+ *
133
+ * @param {string} scriptName - The script name
134
+ * @param {Object} externalInfo - External schedule information
135
+ * @param {string} [externalInfo.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
136
+ * @param {string} [externalInfo.externalScheduleName] - External scheduler name
137
+ * @returns {Promise<Object>} Updated schedule record with string ID
138
+ */
139
+ async updateScheduleExternalInfo(scriptName, { externalScheduleId, externalScheduleName }) {
140
+ const data = {};
141
+ if (externalScheduleId !== undefined) data.externalScheduleId = externalScheduleId;
142
+ if (externalScheduleName !== undefined) data.externalScheduleName = externalScheduleName;
143
+
144
+ const schedule = await this.prisma.scriptSchedule.update({
145
+ where: { scriptName },
146
+ data,
147
+ });
148
+
149
+ return this._convertScheduleIds(schedule);
150
+ }
151
+
152
+ /**
153
+ * Update last triggered timestamp
154
+ *
155
+ * @param {string} scriptName - The script name
156
+ * @param {Date} [timestamp] - Trigger timestamp (default: now)
157
+ * @returns {Promise<Object>} Updated schedule record with string ID
158
+ */
159
+ async updateScheduleLastTriggered(scriptName, timestamp) {
160
+ const schedule = await this.prisma.scriptSchedule.update({
161
+ where: { scriptName },
162
+ data: {
163
+ lastTriggeredAt: timestamp || new Date(),
164
+ },
165
+ });
166
+
167
+ return this._convertScheduleIds(schedule);
168
+ }
169
+
170
+ /**
171
+ * Update next trigger timestamp
172
+ *
173
+ * @param {string} scriptName - The script name
174
+ * @param {Date} timestamp - Next trigger timestamp
175
+ * @returns {Promise<Object>} Updated schedule record with string ID
176
+ */
177
+ async updateScheduleNextTrigger(scriptName, timestamp) {
178
+ const schedule = await this.prisma.scriptSchedule.update({
179
+ where: { scriptName },
180
+ data: {
181
+ nextTriggerAt: timestamp,
182
+ },
183
+ });
184
+
185
+ return this._convertScheduleIds(schedule);
186
+ }
187
+
188
+ /**
189
+ * List all schedules
190
+ *
191
+ * @param {Object} [options] - Query options
192
+ * @param {boolean} [options.enabledOnly] - Only return enabled schedules
193
+ * @returns {Promise<Array>} Array of schedule records with string IDs
194
+ */
195
+ async listSchedules(options = {}) {
196
+ const where = {};
197
+ if (options.enabledOnly) {
198
+ where.enabled = true;
199
+ }
200
+
201
+ const schedules = await this.prisma.scriptSchedule.findMany({
202
+ where,
203
+ orderBy: { scriptName: 'asc' },
204
+ });
205
+
206
+ return schedules.map((schedule) => this._convertScheduleIds(schedule));
207
+ }
208
+ }
209
+
210
+ module.exports = { ScriptScheduleRepositoryPostgres };
@@ -0,0 +1,348 @@
1
+ const ERROR_CODE_MAP = {
2
+ SCRIPT_NOT_FOUND: 404,
3
+ EXECUTION_NOT_FOUND: 404,
4
+ };
5
+
6
+ function mapErrorToResponse(error) {
7
+ const status = ERROR_CODE_MAP[error?.code] || 500;
8
+ return { error: status, reason: error?.message, code: error?.code };
9
+ }
10
+
11
+ /**
12
+ * Create admin script commands
13
+ * Provides command pattern API for admin script management
14
+ *
15
+ * This follows the Command pattern from integration-commands.js:
16
+ * - Creates repositories via factory functions
17
+ * - Maps errors to HTTP-friendly responses
18
+ * - Returns data or error objects (never throws)
19
+ *
20
+ * WHY SEPARATE FROM integration-commands.js:
21
+ * These commands are intentionally separate because they serve different domains:
22
+ * - integration-commands: User-context operations on integrations
23
+ * - Requires integrationClass constructor parameter
24
+ * - Works with userId, entityIds, integration contexts
25
+ * - Uses IntegrationRepository, ModuleRepository
26
+ * - admin-script-commands: System/admin operations without user context
27
+ * - No user context required
28
+ * - Works with AdminProcess, ScriptSchedule
29
+ * - Uses AdminProcessRepository, ScriptScheduleRepository
30
+ *
31
+ * Merging them would violate SRP and create coupling between
32
+ * user-facing integration code and admin/system code.
33
+ *
34
+ * Authentication:
35
+ * - Uses ENV-based ADMIN_API_KEY (see handlers/middleware/admin-auth.js)
36
+ * - No database-backed API keys (simplified from original design)
37
+ *
38
+ * @returns {Object} Command methods for admin scripts
39
+ */
40
+ function createAdminScriptCommands() {
41
+ // Lazy-load repository factories to avoid circular dependencies
42
+ const { createAdminProcessRepository } = require('../../admin-scripts/repositories/admin-process-repository-factory');
43
+ const { createScriptScheduleRepository } = require('../../admin-scripts/repositories/script-schedule-repository-factory');
44
+
45
+ const adminProcessRepository = createAdminProcessRepository();
46
+ const scheduleRepository = createScriptScheduleRepository();
47
+
48
+ return {
49
+ // ==================== Admin Process Management Commands ====================
50
+
51
+ /**
52
+ * Create a new admin process record
53
+ *
54
+ * @param {Object} params - Process creation parameters
55
+ * @param {string} params.scriptName - Name of script being executed
56
+ * @param {string} [params.scriptVersion] - Script version
57
+ * @param {string} params.trigger - Trigger type ('MANUAL', 'SCHEDULED', 'QUEUE', 'WEBHOOK')
58
+ * @param {string} [params.mode] - Execution mode ('sync' or 'async', default 'async')
59
+ * @param {Object} [params.input] - Input parameters
60
+ * @param {Object} [params.audit] - Audit information (apiKeyName, apiKeyLast4, ipAddress)
61
+ * @returns {Promise<Object>} Created admin process record
62
+ */
63
+ async createAdminProcess({
64
+ scriptName,
65
+ scriptVersion,
66
+ trigger,
67
+ mode,
68
+ input,
69
+ audit,
70
+ }) {
71
+ try {
72
+ const process = await adminProcessRepository.createProcess({
73
+ name: scriptName,
74
+ type: 'ADMIN_SCRIPT',
75
+ context: {
76
+ scriptVersion,
77
+ trigger,
78
+ mode: mode || 'async',
79
+ input,
80
+ audit,
81
+ },
82
+ });
83
+ return process;
84
+ } catch (error) {
85
+ return mapErrorToResponse(error);
86
+ }
87
+ },
88
+
89
+ /**
90
+ * Find an admin process by ID
91
+ *
92
+ * @param {string|number} processId - The admin process ID
93
+ * @returns {Promise<Object>} Admin process record or error
94
+ */
95
+ async findAdminProcessById(processId) {
96
+ try {
97
+ const process = await adminProcessRepository.findProcessById(processId);
98
+ if (!process) {
99
+ const error = new Error(`Execution ${processId} not found`);
100
+ error.code = 'EXECUTION_NOT_FOUND';
101
+ return mapErrorToResponse(error);
102
+ }
103
+ return process;
104
+ } catch (error) {
105
+ return mapErrorToResponse(error);
106
+ }
107
+ },
108
+
109
+ /**
110
+ * Find all admin processes for a specific script
111
+ *
112
+ * @param {string} scriptName - Script name to filter by
113
+ * @param {Object} [options] - Query options (limit, offset, sortBy, sortOrder)
114
+ * @returns {Promise<Array>} Array of admin process records
115
+ */
116
+ async findAdminProcessesByName(scriptName, options = {}) {
117
+ try {
118
+ const processes = await adminProcessRepository.findProcessesByName(
119
+ scriptName,
120
+ options
121
+ );
122
+ return processes;
123
+ } catch (error) {
124
+ // Return empty array on error (non-critical)
125
+ return [];
126
+ }
127
+ },
128
+
129
+ /**
130
+ * Update admin process state
131
+ *
132
+ * @param {string|number} processId - The admin process ID
133
+ * @param {string} state - New state ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED')
134
+ * @returns {Promise<Object>} Updated admin process record
135
+ */
136
+ async updateAdminProcessState(processId, state) {
137
+ try {
138
+ const updated = await adminProcessRepository.updateProcessState(
139
+ processId,
140
+ state
141
+ );
142
+ return updated;
143
+ } catch (error) {
144
+ return mapErrorToResponse(error);
145
+ }
146
+ },
147
+
148
+ /**
149
+ * Append a log entry to an admin process's results.logs array
150
+ *
151
+ * @param {string|number} processId - The admin process ID
152
+ * @param {Object} logEntry - Log entry { level, message, data, timestamp }
153
+ * @returns {Promise<Object>} Updated admin process record
154
+ */
155
+ async appendAdminProcessLog(processId, logEntry) {
156
+ try {
157
+ const updated = await adminProcessRepository.appendProcessLog(
158
+ processId,
159
+ logEntry
160
+ );
161
+ return updated;
162
+ } catch (error) {
163
+ return mapErrorToResponse(error);
164
+ }
165
+ },
166
+
167
+ /**
168
+ * Complete an admin process
169
+ * Updates state, output, error, and metrics
170
+ *
171
+ * @param {string|number} processId - The admin process ID
172
+ * @param {Object} params - Completion parameters
173
+ * @param {string} [params.state] - Final state ('COMPLETED', 'FAILED')
174
+ * @param {Object} [params.output] - Script output/result (stored in results.output)
175
+ * @param {Object} [params.error] - Error details { name, message, stack } (stored in results.error)
176
+ * @param {Object} [params.metrics] - Performance metrics { startTime, endTime, durationMs } (stored in results.metrics)
177
+ * @returns {Promise<Object>} { success: true } or error
178
+ */
179
+ async completeAdminProcess(processId, { state, output, error, metrics }) {
180
+ try {
181
+ // Update state if provided
182
+ if (state) {
183
+ await adminProcessRepository.updateProcessState(processId, state);
184
+ }
185
+
186
+ // Build results object from provided fields and merge in one call
187
+ const resultsUpdate = {};
188
+ if (output !== undefined) resultsUpdate.output = output;
189
+ if (error) resultsUpdate.error = error;
190
+ if (metrics) resultsUpdate.metrics = metrics;
191
+
192
+ if (Object.keys(resultsUpdate).length > 0) {
193
+ await adminProcessRepository.updateProcessResults(processId, resultsUpdate);
194
+ }
195
+
196
+ return { success: true };
197
+ } catch (err) {
198
+ return mapErrorToResponse(err);
199
+ }
200
+ },
201
+
202
+ /**
203
+ * Find recent admin processes across all scripts
204
+ *
205
+ * @param {Object} [options] - Query options
206
+ * @param {number} [options.limit] - Maximum results (default 20)
207
+ * @param {string} [options.state] - Filter by state
208
+ * @param {Date} [options.since] - Filter by created date
209
+ * @returns {Promise<Array>} Array of recent admin processes
210
+ */
211
+ async findRecentAdminProcesses(options = {}) {
212
+ try {
213
+ const { limit = 20, state, since } = options;
214
+
215
+ // If state filter provided, use state query
216
+ if (state) {
217
+ return await adminProcessRepository.findProcessesByState(state, {
218
+ limit,
219
+ sortBy: 'createdAt',
220
+ sortOrder: 'desc',
221
+ });
222
+ }
223
+
224
+ // Otherwise, use generic recent query (would need to be added to interface)
225
+ // For now, fall back to empty array if no state filter
226
+ return [];
227
+ } catch (error) {
228
+ return [];
229
+ }
230
+ },
231
+
232
+ // ==================== Schedule Management Commands ====================
233
+
234
+ /**
235
+ * Get schedule by script name
236
+ * Returns database override or null
237
+ *
238
+ * @param {string} scriptName - The script name
239
+ * @returns {Promise<Object|null>} Schedule record or null
240
+ */
241
+ async getScheduleByScriptName(scriptName) {
242
+ try {
243
+ const schedule = await scheduleRepository.findScheduleByScriptName(scriptName);
244
+ return schedule;
245
+ } catch (error) {
246
+ return mapErrorToResponse(error);
247
+ }
248
+ },
249
+
250
+ /**
251
+ * Create or update a schedule (upsert)
252
+ *
253
+ * @param {Object} params - Schedule parameters
254
+ * @param {string} params.scriptName - Name of the script
255
+ * @param {boolean} params.enabled - Whether schedule is enabled
256
+ * @param {string} params.cronExpression - Cron expression
257
+ * @param {string} [params.timezone] - Timezone (default 'UTC')
258
+ * @returns {Promise<Object>} Created or updated schedule
259
+ */
260
+ async upsertSchedule({ scriptName, enabled, cronExpression, timezone }) {
261
+ try {
262
+ const schedule = await scheduleRepository.upsertSchedule({
263
+ scriptName,
264
+ enabled,
265
+ cronExpression,
266
+ timezone,
267
+ });
268
+ return schedule;
269
+ } catch (error) {
270
+ return mapErrorToResponse(error);
271
+ }
272
+ },
273
+
274
+ /**
275
+ * Delete a schedule by script name
276
+ *
277
+ * @param {string} scriptName - The script name
278
+ * @returns {Promise<Object>} Deletion result
279
+ */
280
+ async deleteSchedule(scriptName) {
281
+ try {
282
+ const result = await scheduleRepository.deleteSchedule(scriptName);
283
+ return result;
284
+ } catch (error) {
285
+ return mapErrorToResponse(error);
286
+ }
287
+ },
288
+
289
+ /**
290
+ * Update external scheduler information
291
+ *
292
+ * @param {string} scriptName - The script name
293
+ * @param {Object} externalInfo - External schedule information
294
+ * @param {string} [externalInfo.externalScheduleId] - External scheduler ID (e.g., AWS ARN)
295
+ * @param {string} [externalInfo.externalScheduleName] - External scheduler name
296
+ * @returns {Promise<Object>} Updated schedule
297
+ */
298
+ async updateScheduleExternalInfo(scriptName, { externalScheduleId, externalScheduleName }) {
299
+ try {
300
+ const schedule = await scheduleRepository.updateScheduleExternalInfo(scriptName, {
301
+ externalScheduleId,
302
+ externalScheduleName,
303
+ });
304
+ return schedule;
305
+ } catch (error) {
306
+ return mapErrorToResponse(error);
307
+ }
308
+ },
309
+
310
+ /**
311
+ * Update last triggered timestamp
312
+ * Called when a schedule triggers
313
+ *
314
+ * @param {string} scriptName - The script name
315
+ * @param {Date} [timestamp] - Trigger timestamp (default: now)
316
+ * @returns {Promise<Object>} Updated schedule
317
+ */
318
+ async updateScheduleLastTriggered(scriptName, timestamp) {
319
+ try {
320
+ const schedule = await scheduleRepository.updateScheduleLastTriggered(
321
+ scriptName,
322
+ timestamp
323
+ );
324
+ return schedule;
325
+ } catch (error) {
326
+ return mapErrorToResponse(error);
327
+ }
328
+ },
329
+
330
+ /**
331
+ * List all schedules
332
+ *
333
+ * @param {Object} [options] - Query options
334
+ * @param {boolean} [options.enabledOnly] - Only return enabled schedules
335
+ * @returns {Promise<Array>} Array of schedule records
336
+ */
337
+ async listSchedules(options = {}) {
338
+ try {
339
+ const schedules = await scheduleRepository.listSchedules(options);
340
+ return schedules;
341
+ } catch (error) {
342
+ return [];
343
+ }
344
+ },
345
+ };
346
+ }
347
+
348
+ module.exports = { createAdminScriptCommands };
@@ -152,10 +152,11 @@ function createIntegrationCommands({ integrationClass }) {
152
152
  */
153
153
  async updateIntegrationConfig({ integrationId, config }) {
154
154
  try {
155
- const integration = await integrationRepository.updateIntegrationConfig(
156
- integrationId,
157
- config
158
- );
155
+ const integration =
156
+ await integrationRepository.updateIntegrationConfig(
157
+ integrationId,
158
+ config
159
+ );
159
160
  return integration;
160
161
  } catch (error) {
161
162
  return mapErrorToResponse(error);
@@ -175,10 +176,15 @@ function createIntegrationCommands({ integrationClass }) {
175
176
  throw error;
176
177
  }
177
178
 
178
- const deleted = await integrationRepository.deleteIntegrationById(integrationId);
179
+ const deleted =
180
+ await integrationRepository.deleteIntegrationById(
181
+ integrationId
182
+ );
179
183
 
180
184
  if (!deleted) {
181
- const error = new Error(`Integration ${integrationId} not found`);
185
+ const error = new Error(
186
+ `Integration ${integrationId} not found`
187
+ );
182
188
  error.code = 'INTEGRATION_NOT_FOUND';
183
189
  return mapErrorToResponse(error);
184
190
  }
@@ -4,12 +4,13 @@ const {
4
4
  } = require('./commands/integration-commands');
5
5
  const { createUserCommands } = require('./commands/user-commands');
6
6
  const { createEntityCommands } = require('./commands/entity-commands');
7
- const {
8
- createCredentialCommands,
9
- } = require('./commands/credential-commands');
7
+ const { createCredentialCommands } = require('./commands/credential-commands');
10
8
  const {
11
9
  createSchedulerCommands,
12
10
  } = require('./commands/scheduler-commands');
11
+ const {
12
+ createAdminScriptCommands,
13
+ } = require('./commands/admin-script-commands');
13
14
 
14
15
  /**
15
16
  * Create a unified command factory with all CRUD operations
@@ -61,6 +62,7 @@ module.exports = {
61
62
  createEntityCommands,
62
63
  createCredentialCommands,
63
64
  createSchedulerCommands,
65
+ createAdminScriptCommands,
64
66
 
65
67
  // Legacy standalone function
66
68
  findIntegrationContextByExternalEntityId,
package/assertions/get.js CHANGED
@@ -1,11 +1,21 @@
1
- const lodashGet = require('lodash.get');
2
- const {
3
- RequiredPropertyError,
4
- ParameterTypeError,
5
- } = require('../errors');
1
+ const { RequiredPropertyError, ParameterTypeError } = require('../errors');
2
+
3
+ /**
4
+ * Deep property access by dot-separated path string.
5
+ * Replaces lodash.get (deprecated).
6
+ */
7
+ function deepGet(obj, path, defaultValue) {
8
+ const keys = typeof path === 'string' ? path.split('.') : path;
9
+ let result = obj;
10
+ for (const key of keys) {
11
+ if (result == null) return defaultValue;
12
+ result = result[key];
13
+ }
14
+ return result === undefined ? defaultValue : result;
15
+ }
6
16
 
7
17
  const get = (o, key, defaultValue) => {
8
- const value = lodashGet(o, key, defaultValue);
18
+ const value = deepGet(o, key, defaultValue);
9
19
 
10
20
  if (value !== undefined) {
11
21
  return value;
@@ -26,7 +36,7 @@ const getAll = (o, requiredKeys) => {
26
36
  const returnDict = {};
27
37
 
28
38
  for (const key of requiredKeys) {
29
- const val = lodashGet(o, key);
39
+ const val = deepGet(o, key);
30
40
 
31
41
  if (val) {
32
42
  returnDict[key] = val;