@friggframework/admin-scripts 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.21b69ac.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 (31) hide show
  1. package/PR_517_REVIEW_TRACKER.md +56 -0
  2. package/index.js +12 -3
  3. package/package.json +6 -9
  4. package/src/application/__tests__/admin-frigg-commands.test.js +19 -19
  5. package/src/application/__tests__/admin-script-base.test.js +100 -84
  6. package/src/application/__tests__/script-runner.test.js +146 -16
  7. package/src/application/admin-frigg-commands.js +20 -32
  8. package/src/application/admin-script-base.js +20 -99
  9. package/src/application/script-runner.js +131 -135
  10. package/src/application/use-cases/__tests__/delete-schedule-use-case.test.js +168 -0
  11. package/src/application/use-cases/__tests__/get-effective-schedule-use-case.test.js +114 -0
  12. package/src/application/use-cases/__tests__/upsert-schedule-use-case.test.js +201 -0
  13. package/src/application/use-cases/delete-schedule-use-case.js +108 -0
  14. package/src/application/use-cases/get-effective-schedule-use-case.js +78 -0
  15. package/src/application/use-cases/index.js +18 -0
  16. package/src/application/use-cases/upsert-schedule-use-case.js +127 -0
  17. package/src/builtins/__tests__/integration-health-check.test.js +67 -60
  18. package/src/builtins/__tests__/oauth-token-refresh.test.js +45 -37
  19. package/src/builtins/integration-health-check.js +23 -24
  20. package/src/builtins/oauth-token-refresh.js +19 -20
  21. package/src/infrastructure/__tests__/admin-auth-middleware.test.js +32 -95
  22. package/src/infrastructure/__tests__/admin-script-router.test.js +46 -47
  23. package/src/infrastructure/admin-auth-middleware.js +5 -43
  24. package/src/infrastructure/admin-script-router.js +38 -32
  25. package/src/infrastructure/script-executor-handler.js +2 -2
  26. package/src/application/__tests__/dry-run-http-interceptor.test.js +0 -313
  27. package/src/application/__tests__/dry-run-repository-wrapper.test.js +0 -257
  28. package/src/application/__tests__/schedule-management-use-case.test.js +0 -276
  29. package/src/application/dry-run-http-interceptor.js +0 -296
  30. package/src/application/dry-run-repository-wrapper.js +0 -261
  31. package/src/application/schedule-management-use-case.js +0 -230
@@ -23,7 +23,7 @@ describe('ScriptRunner', () => {
23
23
  config: {
24
24
  timeout: 300000,
25
25
  maxRetries: 0,
26
- requiresIntegrationFactory: false,
26
+ requireIntegrationInstance: false,
27
27
  },
28
28
  };
29
29
 
@@ -36,9 +36,9 @@ describe('ScriptRunner', () => {
36
36
  scriptFactory = new ScriptFactory([TestScript]);
37
37
 
38
38
  mockCommands = {
39
- createScriptExecution: jest.fn(),
40
- updateScriptExecutionStatus: jest.fn(),
41
- completeScriptExecution: jest.fn(),
39
+ createAdminProcess: jest.fn(),
40
+ updateAdminProcessState: jest.fn(),
41
+ completeAdminProcess: jest.fn(),
42
42
  };
43
43
 
44
44
  mockFrigg = {
@@ -49,11 +49,11 @@ describe('ScriptRunner', () => {
49
49
  createAdminScriptCommands.mockReturnValue(mockCommands);
50
50
  createAdminFriggCommands.mockReturnValue(mockFrigg);
51
51
 
52
- mockCommands.createScriptExecution.mockResolvedValue({
52
+ mockCommands.createAdminProcess.mockResolvedValue({
53
53
  id: 'exec-123',
54
54
  });
55
- mockCommands.updateScriptExecutionStatus.mockResolvedValue({});
56
- mockCommands.completeScriptExecution.mockResolvedValue({ success: true });
55
+ mockCommands.updateAdminProcessState.mockResolvedValue({});
56
+ mockCommands.completeAdminProcess.mockResolvedValue({ success: true });
57
57
  });
58
58
 
59
59
  afterEach(() => {
@@ -76,7 +76,7 @@ describe('ScriptRunner', () => {
76
76
  expect(result.executionId).toBe('exec-123');
77
77
  expect(result.metrics.durationMs).toBeGreaterThanOrEqual(0);
78
78
 
79
- expect(mockCommands.createScriptExecution).toHaveBeenCalledWith({
79
+ expect(mockCommands.createAdminProcess).toHaveBeenCalledWith({
80
80
  scriptName: 'test-script',
81
81
  scriptVersion: '1.0.0',
82
82
  trigger: 'MANUAL',
@@ -85,15 +85,15 @@ describe('ScriptRunner', () => {
85
85
  audit: { apiKeyName: 'test-key' },
86
86
  });
87
87
 
88
- expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
88
+ expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
89
89
  'exec-123',
90
90
  'RUNNING'
91
91
  );
92
92
 
93
- expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
93
+ expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
94
94
  'exec-123',
95
95
  expect.objectContaining({
96
- status: 'COMPLETED',
96
+ state: 'COMPLETED',
97
97
  output: { success: true, params: { foo: 'bar' } },
98
98
  metrics: expect.objectContaining({
99
99
  durationMs: expect.any(Number),
@@ -128,10 +128,10 @@ describe('ScriptRunner', () => {
128
128
  expect(result.scriptName).toBe('failing-script');
129
129
  expect(result.error.message).toBe('Script failed');
130
130
 
131
- expect(mockCommands.completeScriptExecution).toHaveBeenCalledWith(
131
+ expect(mockCommands.completeAdminProcess).toHaveBeenCalledWith(
132
132
  'exec-123',
133
133
  expect.objectContaining({
134
- status: 'FAILED',
134
+ state: 'FAILED',
135
135
  error: expect.objectContaining({
136
136
  message: 'Script failed',
137
137
  }),
@@ -146,7 +146,7 @@ describe('ScriptRunner', () => {
146
146
  version: '1.0.0',
147
147
  description: 'Integration script',
148
148
  config: {
149
- requiresIntegrationFactory: true,
149
+ requireIntegrationInstance: true,
150
150
  },
151
151
  };
152
152
 
@@ -178,14 +178,144 @@ describe('ScriptRunner', () => {
178
178
  });
179
179
 
180
180
  expect(result.executionId).toBe('existing-exec-456');
181
- expect(mockCommands.createScriptExecution).not.toHaveBeenCalled();
182
- expect(mockCommands.updateScriptExecutionStatus).toHaveBeenCalledWith(
181
+ expect(mockCommands.createAdminProcess).not.toHaveBeenCalled();
182
+ expect(mockCommands.updateAdminProcessState).toHaveBeenCalledWith(
183
183
  'existing-exec-456',
184
184
  'RUNNING'
185
185
  );
186
186
  });
187
187
  });
188
188
 
189
+ describe('dry-run mode', () => {
190
+ it('should return preview without executing script', async () => {
191
+ const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
192
+
193
+ const result = await runner.execute('test-script', { foo: 'bar' }, {
194
+ trigger: 'MANUAL',
195
+ dryRun: true,
196
+ });
197
+
198
+ expect(result.dryRun).toBe(true);
199
+ expect(result.status).toBe('DRY_RUN_VALID');
200
+ expect(result.scriptName).toBe('test-script');
201
+ expect(result.preview.script.name).toBe('test-script');
202
+ expect(result.preview.script.version).toBe('1.0.0');
203
+ expect(result.preview.input).toEqual({ foo: 'bar' });
204
+ expect(result.message).toContain('validation passed');
205
+
206
+ // Should NOT create execution record or call commands
207
+ expect(mockCommands.createAdminProcess).not.toHaveBeenCalled();
208
+ expect(mockCommands.updateAdminProcessState).not.toHaveBeenCalled();
209
+ expect(mockCommands.completeAdminProcess).not.toHaveBeenCalled();
210
+ });
211
+
212
+ it('should validate required parameters in dry-run', async () => {
213
+ class SchemaScript extends AdminScriptBase {
214
+ static Definition = {
215
+ name: 'schema-script',
216
+ version: '1.0.0',
217
+ description: 'Script with schema',
218
+ inputSchema: {
219
+ type: 'object',
220
+ required: ['requiredParam'],
221
+ properties: {
222
+ requiredParam: { type: 'string' },
223
+ optionalParam: { type: 'number' },
224
+ },
225
+ },
226
+ };
227
+
228
+ async execute() {
229
+ return {};
230
+ }
231
+ }
232
+
233
+ scriptFactory.register(SchemaScript);
234
+ const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
235
+
236
+ // Missing required parameter
237
+ const result = await runner.execute('schema-script', {}, {
238
+ dryRun: true,
239
+ });
240
+
241
+ expect(result.status).toBe('DRY_RUN_INVALID');
242
+ expect(result.preview.validation.valid).toBe(false);
243
+ expect(result.preview.validation.errors).toContain('Missing required parameter: requiredParam');
244
+ });
245
+
246
+ it('should validate parameter types in dry-run', async () => {
247
+ class TypedScript extends AdminScriptBase {
248
+ static Definition = {
249
+ name: 'typed-script',
250
+ version: '1.0.0',
251
+ description: 'Script with typed params',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ count: { type: 'integer' },
256
+ name: { type: 'string' },
257
+ enabled: { type: 'boolean' },
258
+ },
259
+ },
260
+ };
261
+
262
+ async execute() {
263
+ return {};
264
+ }
265
+ }
266
+
267
+ scriptFactory.register(TypedScript);
268
+ const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
269
+
270
+ const result = await runner.execute('typed-script', {
271
+ count: 'not-a-number',
272
+ name: 123,
273
+ enabled: 'true',
274
+ }, {
275
+ dryRun: true,
276
+ });
277
+
278
+ expect(result.status).toBe('DRY_RUN_INVALID');
279
+ expect(result.preview.validation.errors).toHaveLength(3);
280
+ });
281
+
282
+ it('should pass validation with correct parameters', async () => {
283
+ class ValidScript extends AdminScriptBase {
284
+ static Definition = {
285
+ name: 'valid-script',
286
+ version: '1.0.0',
287
+ description: 'Script for validation',
288
+ inputSchema: {
289
+ type: 'object',
290
+ required: ['name'],
291
+ properties: {
292
+ name: { type: 'string' },
293
+ count: { type: 'integer' },
294
+ },
295
+ },
296
+ };
297
+
298
+ async execute() {
299
+ return {};
300
+ }
301
+ }
302
+
303
+ scriptFactory.register(ValidScript);
304
+ const runner = new ScriptRunner({ scriptFactory, commands: mockCommands });
305
+
306
+ const result = await runner.execute('valid-script', {
307
+ name: 'test',
308
+ count: 42,
309
+ }, {
310
+ dryRun: true,
311
+ });
312
+
313
+ expect(result.status).toBe('DRY_RUN_VALID');
314
+ expect(result.preview.validation.valid).toBe(true);
315
+ expect(result.preview.validation.errors).toHaveLength(0);
316
+ });
317
+ });
318
+
189
319
  describe('createScriptRunner()', () => {
190
320
  it('should create runner with default factory', () => {
191
321
  const runner = createScriptRunner();
@@ -1,18 +1,6 @@
1
1
  const { QueuerUtil } = require('@friggframework/core/queues');
2
2
 
3
- /**
4
- * AdminFriggCommands
5
- *
6
- * Helper API for admin scripts. Provides:
7
- * - Database access via repositories
8
- * - Integration instantiation (optional)
9
- * - Logging utilities
10
- * - Queue operations for self-queuing pattern
11
- *
12
- * Follows lazy-loading pattern for repositories to avoid circular dependencies
13
- * and unnecessary initialization.
14
- */
15
- class AdminFriggCommands {
3
+ class AdminScriptContext {
16
4
  constructor(params = {}) {
17
5
  this.executionId = params.executionId || null;
18
6
  this.logs = [];
@@ -25,7 +13,7 @@ class AdminFriggCommands {
25
13
  this._userRepository = null;
26
14
  this._moduleRepository = null;
27
15
  this._credentialRepository = null;
28
- this._scriptExecutionRepository = null;
16
+ this._adminProcessRepository = null;
29
17
  }
30
18
 
31
19
  // ==================== LAZY-LOADED REPOSITORIES ====================
@@ -62,12 +50,12 @@ class AdminFriggCommands {
62
50
  return this._credentialRepository;
63
51
  }
64
52
 
65
- get scriptExecutionRepository() {
66
- if (!this._scriptExecutionRepository) {
67
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
68
- this._scriptExecutionRepository = createScriptExecutionRepository();
53
+ get adminProcessRepository() {
54
+ if (!this._adminProcessRepository) {
55
+ const { createAdminProcessRepository } = require('@friggframework/core/admin-scripts/repositories/admin-process-repository-factory');
56
+ this._adminProcessRepository = createAdminProcessRepository();
69
57
  }
70
- return this._scriptExecutionRepository;
58
+ return this._adminProcessRepository;
71
59
  }
72
60
 
73
61
  // ==================== INTEGRATION QUERIES ====================
@@ -142,7 +130,7 @@ class AdminFriggCommands {
142
130
  if (!this.integrationFactory) {
143
131
  throw new Error(
144
132
  'instantiate() requires integrationFactory. ' +
145
- 'Set Definition.config.requiresIntegrationFactory = true'
133
+ 'Set Definition.config.requireIntegrationInstance = true'
146
134
  );
147
135
  }
148
136
  return this.integrationFactory.getInstanceFromIntegrationId({
@@ -151,12 +139,8 @@ class AdminFriggCommands {
151
139
  });
152
140
  }
153
141
 
154
- // ==================== QUEUE OPERATIONS (Self-Queuing Pattern) ====================
142
+ // ==================== QUEUE OPERATIONS ====================
155
143
 
156
- /**
157
- * Queue a script for execution
158
- * Used for self-queuing pattern with long-running scripts
159
- */
160
144
  async queueScript(scriptName, params = {}) {
161
145
  const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
162
146
  if (!queueUrl) {
@@ -176,9 +160,6 @@ class AdminFriggCommands {
176
160
  this.log('info', `Queued continuation for ${scriptName}`, { params });
177
161
  }
178
162
 
179
- /**
180
- * Queue multiple scripts in a batch
181
- */
182
163
  async queueScriptBatch(entries) {
183
164
  const queueUrl = process.env.ADMIN_SCRIPT_QUEUE_URL;
184
165
  if (!queueUrl) {
@@ -209,7 +190,7 @@ class AdminFriggCommands {
209
190
 
210
191
  // Persist to execution record if we have an executionId
211
192
  if (this.executionId) {
212
- this.scriptExecutionRepository.appendExecutionLog(this.executionId, entry)
193
+ this.adminProcessRepository.appendProcessLog(this.executionId, entry)
213
194
  .catch(err => console.error('Failed to persist log:', err));
214
195
  }
215
196
 
@@ -230,13 +211,20 @@ class AdminFriggCommands {
230
211
  }
231
212
 
232
213
  /**
233
- * Create AdminFriggCommands instance
214
+ * Create AdminScriptContext instance
234
215
  */
235
- function createAdminFriggCommands(params = {}) {
236
- return new AdminFriggCommands(params);
216
+ function createAdminScriptContext(params = {}) {
217
+ return new AdminScriptContext(params);
237
218
  }
238
219
 
220
+ // Legacy aliases for backwards compatibility
221
+ const AdminFriggCommands = AdminScriptContext;
222
+ const createAdminFriggCommands = createAdminScriptContext;
223
+
239
224
  module.exports = {
225
+ AdminScriptContext,
226
+ createAdminScriptContext,
227
+ // Legacy exports (deprecated)
240
228
  AdminFriggCommands,
241
229
  createAdminFriggCommands,
242
230
  };
@@ -1,64 +1,27 @@
1
- const { createScriptExecutionRepository } = require('@friggframework/core/admin-scripts/repositories/script-execution-repository-factory');
2
- const { createAdminApiKeyRepository } = require('@friggframework/core/admin-scripts/repositories/admin-api-key-repository-factory');
3
-
4
- /**
5
- * Admin Script Base Class
6
- *
7
- * Base class for all admin scripts. Provides:
8
- * - Standard script definition pattern
9
- * - Repository access
10
- * - Logging helpers
11
- * - Integration factory support (optional)
12
- *
13
- * Usage:
14
- * ```javascript
15
- * class MyScript extends AdminScriptBase {
16
- * static Definition = {
17
- * name: 'my-script',
18
- * version: '1.0.0',
19
- * description: 'Does something useful',
20
- * ...
21
- * };
22
- *
23
- * async execute(frigg, params) {
24
- * // Your script logic here
25
- * }
26
- * }
27
- * ```
28
- */
29
1
  class AdminScriptBase {
30
- /**
31
- * CHILDREN SHOULD SPECIFY A DEFINITION FOR THE SCRIPT
32
- * Pattern matches IntegrationBase.Definition
33
- */
34
2
  static Definition = {
35
- name: 'Script Name', // Required: unique identifier
36
- version: '0.0.0', // Required: semver for migrations
37
- description: 'What this script does', // Required: human-readable
38
-
39
- // Script-specific properties
3
+ name: 'Script Name',
4
+ version: '0.0.0',
5
+ description: 'What this script does',
40
6
  source: 'USER_DEFINED', // 'BUILTIN' | 'USER_DEFINED'
41
7
 
42
- inputSchema: null, // Optional: JSON Schema for params
43
- outputSchema: null, // Optional: JSON Schema for results
8
+ inputSchema: null,
9
+ outputSchema: null,
44
10
 
45
11
  schedule: {
46
- // Optional: Phase 2
47
12
  enabled: false,
48
- cronExpression: null, // 'cron(0 12 * * ? *)'
13
+ cronExpression: null,
49
14
  },
50
15
 
51
16
  config: {
52
- timeout: 300000, // Default 5 min (ms)
17
+ timeout: 300000,
53
18
  maxRetries: 0,
54
- requiresIntegrationFactory: false, // Hint: does script need to instantiate integrations?
19
+ requireIntegrationInstance: false,
55
20
  },
56
21
 
57
22
  display: {
58
- // For future UI
59
- label: 'Script Name',
60
- description: '',
61
- category: 'maintenance', // 'maintenance' | 'healing' | 'sync' | 'custom'
23
+ category: 'maintenance',
24
+ icon: null,
62
25
  },
63
26
  };
64
27
 
@@ -74,64 +37,22 @@ class AdminScriptBase {
74
37
  return this.Definition;
75
38
  }
76
39
 
77
- /**
78
- * Constructor receives dependencies
79
- * Pattern matches IntegrationBase constructor
80
- */
81
- constructor(params = {}) {
82
- this.executionId = params.executionId || null;
83
- this.logs = [];
84
- this._startTime = null;
85
-
86
- // OPTIONAL: Integration factory for scripts that need it
87
- this.integrationFactory = params.integrationFactory || null;
88
-
89
- // OPTIONAL: Injected repositories (for testing or custom implementations)
90
- this.scriptExecutionRepository = params.scriptExecutionRepository || null;
91
- this.adminApiKeyRepository = params.adminApiKeyRepository || null;
92
- }
93
-
94
- /**
95
- * CHILDREN MUST IMPLEMENT THIS METHOD
96
- * @param {AdminFriggCommands} frigg - Helper commands object
97
- * @param {Object} params - Script parameters (validated against inputSchema)
98
- * @returns {Promise<Object>} - Script results (validated against outputSchema)
99
- */
100
- async execute(frigg, params) {
101
- throw new Error('AdminScriptBase.execute() must be implemented by subclass');
40
+ static getDisplayLabel() {
41
+ return this.Definition.display?.label || this.Definition.name;
102
42
  }
103
43
 
104
- /**
105
- * Logging helper
106
- * @param {string} level - Log level (info, warn, error, debug)
107
- * @param {string} message - Log message
108
- * @param {Object} data - Additional data
109
- * @returns {Object} Log entry
110
- */
111
- log(level, message, data = {}) {
112
- const entry = {
113
- level,
114
- message,
115
- data,
116
- timestamp: new Date().toISOString(),
117
- };
118
- this.logs.push(entry);
119
- return entry;
44
+ static getDisplayDescription() {
45
+ return this.Definition.display?.description || this.Definition.description;
120
46
  }
121
47
 
122
- /**
123
- * Get all logs
124
- * @returns {Array} Log entries
125
- */
126
- getLogs() {
127
- return this.logs;
48
+ constructor(params = {}) {
49
+ this.context = params.context || null;
50
+ this.executionId = params.executionId || null;
51
+ this.integrationFactory = params.integrationFactory || null;
128
52
  }
129
53
 
130
- /**
131
- * Clear all logs
132
- */
133
- clearLogs() {
134
- this.logs = [];
54
+ async execute(params) {
55
+ throw new Error('AdminScriptBase.execute() must be implemented by subclass');
135
56
  }
136
57
  }
137
58