@friggframework/admin-scripts 2.0.0--canary.517.41839c5.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 (35) hide show
  1. package/LICENSE.md +9 -0
  2. package/index.js +66 -0
  3. package/package.json +53 -0
  4. package/src/adapters/__tests__/aws-scheduler-adapter.test.js +322 -0
  5. package/src/adapters/__tests__/local-scheduler-adapter.test.js +325 -0
  6. package/src/adapters/__tests__/scheduler-adapter-factory.test.js +257 -0
  7. package/src/adapters/__tests__/scheduler-adapter.test.js +103 -0
  8. package/src/adapters/aws-scheduler-adapter.js +138 -0
  9. package/src/adapters/local-scheduler-adapter.js +103 -0
  10. package/src/adapters/scheduler-adapter-factory.js +69 -0
  11. package/src/adapters/scheduler-adapter.js +64 -0
  12. package/src/application/__tests__/admin-frigg-commands.test.js +643 -0
  13. package/src/application/__tests__/admin-script-base.test.js +273 -0
  14. package/src/application/__tests__/dry-run-http-interceptor.test.js +313 -0
  15. package/src/application/__tests__/dry-run-repository-wrapper.test.js +257 -0
  16. package/src/application/__tests__/schedule-management-use-case.test.js +276 -0
  17. package/src/application/__tests__/script-factory.test.js +381 -0
  18. package/src/application/__tests__/script-runner.test.js +202 -0
  19. package/src/application/admin-frigg-commands.js +242 -0
  20. package/src/application/admin-script-base.js +138 -0
  21. package/src/application/dry-run-http-interceptor.js +296 -0
  22. package/src/application/dry-run-repository-wrapper.js +261 -0
  23. package/src/application/schedule-management-use-case.js +230 -0
  24. package/src/application/script-factory.js +161 -0
  25. package/src/application/script-runner.js +254 -0
  26. package/src/builtins/__tests__/integration-health-check.test.js +598 -0
  27. package/src/builtins/__tests__/oauth-token-refresh.test.js +344 -0
  28. package/src/builtins/index.js +28 -0
  29. package/src/builtins/integration-health-check.js +279 -0
  30. package/src/builtins/oauth-token-refresh.js +221 -0
  31. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +148 -0
  32. package/src/infrastructure/__tests__/admin-script-router.test.js +701 -0
  33. package/src/infrastructure/admin-auth-middleware.js +49 -0
  34. package/src/infrastructure/admin-script-router.js +311 -0
  35. package/src/infrastructure/script-executor-handler.js +75 -0
@@ -0,0 +1,254 @@
1
+ const { getScriptFactory } = require('./script-factory');
2
+ const { createAdminFriggCommands } = require('./admin-frigg-commands');
3
+ const { createAdminScriptCommands } = require('@friggframework/core/application/commands/admin-script-commands');
4
+ const { wrapAdminFriggCommandsForDryRun } = require('./dry-run-repository-wrapper');
5
+ const { createDryRunHttpClient, injectDryRunHttpClient } = require('./dry-run-http-interceptor');
6
+
7
+ /**
8
+ * Script Runner
9
+ *
10
+ * Orchestrates script execution with:
11
+ * - Execution record creation
12
+ * - Script instantiation
13
+ * - AdminFriggCommands injection
14
+ * - Error handling
15
+ * - Status updates
16
+ */
17
+ class ScriptRunner {
18
+ constructor(params = {}) {
19
+ this.scriptFactory = params.scriptFactory || getScriptFactory();
20
+ this.commands = params.commands || createAdminScriptCommands();
21
+ this.integrationFactory = params.integrationFactory || null;
22
+ }
23
+
24
+ /**
25
+ * Execute a script
26
+ * @param {string} scriptName - Name of the script to run
27
+ * @param {Object} params - Script parameters
28
+ * @param {Object} options - Execution options
29
+ * @param {string} options.trigger - 'MANUAL' | 'SCHEDULED' | 'QUEUE'
30
+ * @param {string} options.mode - 'sync' | 'async'
31
+ * @param {Object} options.audit - Audit info { apiKeyName, apiKeyLast4, ipAddress }
32
+ * @param {string} options.executionId - Reuse existing execution ID
33
+ * @param {boolean} options.dryRun - Execute in dry-run mode (no writes, log operations)
34
+ */
35
+ async execute(scriptName, params = {}, options = {}) {
36
+ const { trigger = 'MANUAL', audit = {}, executionId: existingExecutionId, dryRun = false } = options;
37
+
38
+ // Get script class
39
+ const scriptClass = this.scriptFactory.get(scriptName);
40
+ const definition = scriptClass.Definition;
41
+
42
+ // Validate integrationFactory requirement
43
+ if (definition.config?.requiresIntegrationFactory && !this.integrationFactory) {
44
+ throw new Error(
45
+ `Script "${scriptName}" requires integrationFactory but none was provided`
46
+ );
47
+ }
48
+
49
+ let executionId = existingExecutionId;
50
+
51
+ // Create execution record if not provided
52
+ if (!executionId) {
53
+ const execution = await this.commands.createScriptExecution({
54
+ scriptName,
55
+ scriptVersion: definition.version,
56
+ trigger,
57
+ mode: options.mode || 'async',
58
+ input: params,
59
+ audit,
60
+ });
61
+ executionId = execution.id;
62
+ }
63
+
64
+ const startTime = new Date();
65
+
66
+ try {
67
+ // Update status to RUNNING (skip in dry-run)
68
+ if (!dryRun) {
69
+ await this.commands.updateScriptExecutionStatus(executionId, 'RUNNING');
70
+ }
71
+
72
+ // Create frigg commands for the script
73
+ let frigg;
74
+ let operationLog = [];
75
+
76
+ if (dryRun) {
77
+ // Dry-run mode: wrap commands to intercept writes
78
+ frigg = this.createDryRunFriggCommands(operationLog);
79
+ } else {
80
+ // Normal mode: create real commands
81
+ frigg = createAdminFriggCommands({
82
+ executionId,
83
+ integrationFactory: this.integrationFactory,
84
+ });
85
+ }
86
+
87
+ // Create script instance
88
+ const script = this.scriptFactory.createInstance(scriptName, {
89
+ executionId,
90
+ integrationFactory: this.integrationFactory,
91
+ });
92
+
93
+ // Execute the script
94
+ const output = await script.execute(frigg, params);
95
+
96
+ // Calculate metrics
97
+ const endTime = new Date();
98
+ const durationMs = endTime - startTime;
99
+
100
+ // Complete execution (skip in dry-run)
101
+ if (!dryRun) {
102
+ await this.commands.completeScriptExecution(executionId, {
103
+ status: 'COMPLETED',
104
+ output,
105
+ metrics: {
106
+ startTime: startTime.toISOString(),
107
+ endTime: endTime.toISOString(),
108
+ durationMs,
109
+ },
110
+ });
111
+ }
112
+
113
+ // Return dry-run preview if in dry-run mode
114
+ if (dryRun) {
115
+ return {
116
+ executionId,
117
+ dryRun: true,
118
+ status: 'DRY_RUN_COMPLETED',
119
+ scriptName,
120
+ preview: {
121
+ operations: operationLog,
122
+ summary: this.summarizeOperations(operationLog),
123
+ scriptOutput: output,
124
+ },
125
+ metrics: { durationMs },
126
+ };
127
+ }
128
+
129
+ return {
130
+ executionId,
131
+ status: 'COMPLETED',
132
+ scriptName,
133
+ output,
134
+ metrics: { durationMs },
135
+ };
136
+ } catch (error) {
137
+ // Calculate metrics even on failure
138
+ const endTime = new Date();
139
+ const durationMs = endTime - startTime;
140
+
141
+ // Record failure (skip in dry-run)
142
+ if (!dryRun) {
143
+ await this.commands.completeScriptExecution(executionId, {
144
+ status: 'FAILED',
145
+ error: {
146
+ name: error.name,
147
+ message: error.message,
148
+ stack: error.stack,
149
+ },
150
+ metrics: {
151
+ startTime: startTime.toISOString(),
152
+ endTime: endTime.toISOString(),
153
+ durationMs,
154
+ },
155
+ });
156
+ }
157
+
158
+ return {
159
+ executionId,
160
+ dryRun,
161
+ status: dryRun ? 'DRY_RUN_FAILED' : 'FAILED',
162
+ scriptName,
163
+ error: {
164
+ name: error.name,
165
+ message: error.message,
166
+ },
167
+ metrics: { durationMs },
168
+ };
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Create dry-run version of AdminFriggCommands
174
+ * Intercepts all write operations and logs them
175
+ *
176
+ * @param {Array} operationLog - Array to collect logged operations
177
+ * @returns {Object} Wrapped AdminFriggCommands
178
+ */
179
+ createDryRunFriggCommands(operationLog) {
180
+ // Create real commands (for read operations)
181
+ const realCommands = createAdminFriggCommands({
182
+ executionId: null, // Don't persist logs in dry-run
183
+ integrationFactory: this.integrationFactory,
184
+ });
185
+
186
+ // Wrap commands to intercept writes
187
+ const wrappedCommands = wrapAdminFriggCommandsForDryRun(realCommands, operationLog);
188
+
189
+ // Create dry-run HTTP client
190
+ const dryRunHttpClient = createDryRunHttpClient(operationLog);
191
+
192
+ // Override instantiate to inject dry-run HTTP client
193
+ const originalInstantiate = wrappedCommands.instantiate.bind(wrappedCommands);
194
+ wrappedCommands.instantiate = async (integrationId) => {
195
+ const instance = await originalInstantiate(integrationId);
196
+
197
+ // Inject dry-run HTTP client into the integration instance
198
+ injectDryRunHttpClient(instance, dryRunHttpClient);
199
+
200
+ return instance;
201
+ };
202
+
203
+ return wrappedCommands;
204
+ }
205
+
206
+ /**
207
+ * Summarize operations from dry-run log
208
+ *
209
+ * @param {Array} log - Operation log
210
+ * @returns {Object} Summary statistics
211
+ */
212
+ summarizeOperations(log) {
213
+ const summary = {
214
+ totalOperations: log.length,
215
+ databaseWrites: 0,
216
+ httpRequests: 0,
217
+ byOperation: {},
218
+ byModel: {},
219
+ byService: {},
220
+ };
221
+
222
+ for (const op of log) {
223
+ // Count by operation type
224
+ const operation = op.operation || op.method || 'UNKNOWN';
225
+ summary.byOperation[operation] = (summary.byOperation[operation] || 0) + 1;
226
+
227
+ // Database operations
228
+ if (op.model) {
229
+ summary.databaseWrites++;
230
+ summary.byModel[op.model] = summary.byModel[op.model] || [];
231
+ summary.byModel[op.model].push({
232
+ operation: op.operation,
233
+ method: op.method,
234
+ timestamp: op.timestamp,
235
+ });
236
+ }
237
+
238
+ // HTTP requests
239
+ if (op.operation === 'HTTP_REQUEST') {
240
+ summary.httpRequests++;
241
+ const service = op.service || 'unknown';
242
+ summary.byService[service] = (summary.byService[service] || 0) + 1;
243
+ }
244
+ }
245
+
246
+ return summary;
247
+ }
248
+ }
249
+
250
+ function createScriptRunner(params = {}) {
251
+ return new ScriptRunner(params);
252
+ }
253
+
254
+ module.exports = { ScriptRunner, createScriptRunner };