@friggframework/admin-scripts 2.0.0--canary.517.c197eb5.0 → 2.0.0--canary.517.216685f.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.
package/README.md CHANGED
@@ -76,8 +76,8 @@ class AttioHealingScript extends AdminScriptBase {
76
76
  requireIntegrationInstance: true, // needs this.context.instantiate()
77
77
  },
78
78
 
79
- // Optional default schedule (can be overridden at runtime via the API)
80
- schedule: { enabled: false, cronExpression: 'cron(0 12 * * ? *)' },
79
+ // No schedule here a script is a capability. An admin activates a
80
+ // recurring run at runtime via PUT /admin/scripts/:name/schedule.
81
81
 
82
82
  display: { category: 'maintenance' },
83
83
  };
@@ -203,7 +203,7 @@ curl "https://<your-app>/admin/scripts/attio-healing/executions?status=FAILED&li
203
203
 
204
204
  ## Scheduling
205
205
 
206
- A script can ship a default schedule in its `Definition.schedule`, and operators can override it at runtime. The **effective** schedule resolves as: runtime override (DB) definition default none.
206
+ Scripts don't declare a schedule — a script is a capability. An admin activates a recurring run at runtime via `PUT .../schedule`, which persists the schedule and provisions it in EventBridge. The **effective** schedule is therefore either the runtime override (DB) or none — there is no code-defined default that fires on its own.
207
207
 
208
208
  ```bash
209
209
  # Enable a daily 6am UTC run
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.c197eb5.0",
4
+ "version": "2.0.0--canary.517.216685f.0",
5
5
  "description": "Admin Script Runner for Frigg - Execute maintenance and operational scripts in hosted environments",
6
6
  "dependencies": {
7
7
  "@aws-sdk/client-scheduler": "^3.588.0",
8
- "@friggframework/core": "2.0.0--canary.517.c197eb5.0",
8
+ "@friggframework/core": "2.0.0--canary.517.216685f.0",
9
9
  "@hapi/boom": "^10.0.1",
10
10
  "express": "^4.18.2",
11
11
  "serverless-http": "^3.2.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@friggframework/eslint-config": "2.0.0--canary.517.c197eb5.0",
15
- "@friggframework/prettier-config": "2.0.0--canary.517.c197eb5.0",
16
- "@friggframework/test": "2.0.0--canary.517.c197eb5.0",
14
+ "@friggframework/eslint-config": "2.0.0--canary.517.216685f.0",
15
+ "@friggframework/prettier-config": "2.0.0--canary.517.216685f.0",
16
+ "@friggframework/test": "2.0.0--canary.517.216685f.0",
17
17
  "eslint": "^8.22.0",
18
18
  "jest": "^29.7.0",
19
19
  "prettier": "^2.7.1",
@@ -44,5 +44,5 @@
44
44
  "maintenance",
45
45
  "operations"
46
46
  ],
47
- "gitHead": "c197eb5c237e263a8d1b65a94f213ee4732b3c57"
47
+ "gitHead": "216685fea6d163571fffe8203df76a9a2f05e933"
48
48
  }
@@ -18,16 +18,11 @@ describe('AdminScriptBase', () => {
18
18
  name: 'test-script',
19
19
  version: '1.0.0',
20
20
  description: 'A test script',
21
- source: 'BUILTIN',
21
+ source: 'USER_DEFINED',
22
22
  inputSchema: { type: 'object' },
23
23
  outputSchema: { type: 'object' },
24
- schedule: {
25
- enabled: true,
26
- cronExpression: 'cron(0 12 * * ? *)',
27
- },
28
24
  config: {
29
25
  timeout: 600000,
30
- maxRetries: 3,
31
26
  requireIntegrationInstance: true,
32
27
  },
33
28
  display: {
@@ -40,8 +35,7 @@ describe('AdminScriptBase', () => {
40
35
  expect(TestScript.Definition.name).toBe('test-script');
41
36
  expect(TestScript.Definition.version).toBe('1.0.0');
42
37
  expect(TestScript.Definition.description).toBe('A test script');
43
- expect(TestScript.Definition.source).toBe('BUILTIN');
44
- expect(TestScript.Definition.schedule.enabled).toBe(true);
38
+ expect(TestScript.Definition.source).toBe('USER_DEFINED');
45
39
  expect(TestScript.Definition.config.timeout).toBe(600000);
46
40
  });
47
41
 
@@ -8,11 +8,6 @@ class AdminScriptBase {
8
8
  inputSchema: null,
9
9
  outputSchema: null,
10
10
 
11
- schedule: {
12
- enabled: false,
13
- cronExpression: null,
14
- },
15
-
16
11
  config: {
17
12
  timeout: 300000,
18
13
  requireIntegrationInstance: false,
@@ -88,33 +88,7 @@ describe('DeleteScheduleUseCase', () => {
88
88
  expect(result.schedulerWarning).toBe('Scheduler delete failed');
89
89
  });
90
90
 
91
- it('should return definition schedule as effective after deletion', async () => {
92
- const definitionSchedule = {
93
- enabled: true,
94
- cronExpression: '0 6 * * *',
95
- timezone: 'America/Los_Angeles',
96
- };
97
-
98
- mockScriptFactory.has.mockReturnValue(true);
99
- mockScriptFactory.get.mockReturnValue({
100
- Definition: { schedule: definitionSchedule },
101
- });
102
- mockCommands.deleteSchedule.mockResolvedValue({
103
- deletedCount: 1,
104
- deleted: { scriptName: 'test-script' },
105
- });
106
-
107
- const result = await useCase.execute('test-script');
108
-
109
- expect(result.effectiveSchedule.source).toBe('definition');
110
- expect(result.effectiveSchedule.enabled).toBe(true);
111
- expect(result.effectiveSchedule.cronExpression).toBe('0 6 * * *');
112
- expect(result.effectiveSchedule.timezone).toBe(
113
- 'America/Los_Angeles'
114
- );
115
- });
116
-
117
- it('should default timezone to UTC when not in definition', async () => {
91
+ it('always returns none after deletion, ignoring any Definition schedule', async () => {
118
92
  mockScriptFactory.has.mockReturnValue(true);
119
93
  mockScriptFactory.get.mockReturnValue({
120
94
  Definition: {
@@ -128,10 +102,11 @@ describe('DeleteScheduleUseCase', () => {
128
102
 
129
103
  const result = await useCase.execute('test-script');
130
104
 
131
- expect(result.effectiveSchedule.timezone).toBe('UTC');
105
+ expect(result.effectiveSchedule.source).toBe('none');
106
+ expect(result.effectiveSchedule.enabled).toBe(false);
132
107
  });
133
108
 
134
- it('should return none as effective when no definition schedule', async () => {
109
+ it('should return none as effective after deletion', async () => {
135
110
  mockScriptFactory.has.mockReturnValue(true);
136
111
  mockScriptFactory.get.mockReturnValue({ Definition: {} });
137
112
  mockCommands.deleteSchedule.mockResolvedValue({
@@ -42,41 +42,6 @@ describe('GetEffectiveScheduleUseCase', () => {
42
42
  expect(result.schedule).toEqual(dbSchedule);
43
43
  });
44
44
 
45
- it('should return definition schedule when no database override', async () => {
46
- const definitionSchedule = {
47
- enabled: true,
48
- cronExpression: '0 12 * * *',
49
- timezone: 'America/New_York',
50
- };
51
-
52
- mockScriptFactory.has.mockReturnValue(true);
53
- mockScriptFactory.get.mockReturnValue({
54
- Definition: { schedule: definitionSchedule },
55
- });
56
- mockCommands.getScheduleByScriptName.mockResolvedValue(null);
57
-
58
- const result = await useCase.execute('test-script');
59
-
60
- expect(result.source).toBe('definition');
61
- expect(result.schedule.enabled).toBe(true);
62
- expect(result.schedule.cronExpression).toBe('0 12 * * *');
63
- expect(result.schedule.timezone).toBe('America/New_York');
64
- });
65
-
66
- it('should default timezone to UTC when not specified in definition', async () => {
67
- mockScriptFactory.has.mockReturnValue(true);
68
- mockScriptFactory.get.mockReturnValue({
69
- Definition: {
70
- schedule: { enabled: true, cronExpression: '0 12 * * *' },
71
- },
72
- });
73
- mockCommands.getScheduleByScriptName.mockResolvedValue(null);
74
-
75
- const result = await useCase.execute('test-script');
76
-
77
- expect(result.schedule.timezone).toBe('UTC');
78
- });
79
-
80
45
  it('should return none when no schedule configured', async () => {
81
46
  mockScriptFactory.has.mockReturnValue(true);
82
47
  mockScriptFactory.get.mockReturnValue({ Definition: {} });
@@ -89,10 +54,12 @@ describe('GetEffectiveScheduleUseCase', () => {
89
54
  expect(result.schedule.scriptName).toBe('test-script');
90
55
  });
91
56
 
92
- it('should return none when definition schedule is disabled', async () => {
57
+ it('ignores any schedule declared in the Definition (DB override is the only source)', async () => {
93
58
  mockScriptFactory.has.mockReturnValue(true);
94
59
  mockScriptFactory.get.mockReturnValue({
95
- Definition: { schedule: { enabled: false } },
60
+ Definition: {
61
+ schedule: { enabled: true, cronExpression: '0 12 * * *' },
62
+ },
96
63
  });
97
64
  mockCommands.getScheduleByScriptName.mockResolvedValue(null);
98
65
 
@@ -6,7 +6,6 @@ const Boom = require('@hapi/boom');
6
6
  * Application Layer - Hexagonal Architecture
7
7
  *
8
8
  * Deletes a schedule override and cleans up external scheduler resources.
9
- * Returns the effective schedule after deletion (may fall back to definition).
10
9
  */
11
10
  class DeleteScheduleUseCase {
12
11
  constructor({ commands, schedulerAdapter, scriptFactory }) {
@@ -32,9 +31,7 @@ class DeleteScheduleUseCase {
32
31
  deleteResult.deleted?.externalScheduleId
33
32
  );
34
33
 
35
- // Determine effective schedule after deletion
36
- const effectiveSchedule =
37
- this._getEffectiveScheduleAfterDeletion(scriptName);
34
+ const effectiveSchedule = { source: 'none', enabled: false };
38
35
 
39
36
  return {
40
37
  success: true,
@@ -57,37 +54,6 @@ class DeleteScheduleUseCase {
57
54
  }
58
55
  }
59
56
 
60
- /**
61
- * Get the definition schedule from a script class
62
- * @private
63
- */
64
- _getDefinitionSchedule(scriptName) {
65
- const scriptClass = this.scriptFactory.get(scriptName);
66
- return scriptClass.Definition?.schedule || null;
67
- }
68
-
69
- /**
70
- * Determine effective schedule after deletion
71
- * @private
72
- */
73
- _getEffectiveScheduleAfterDeletion(scriptName) {
74
- const definitionSchedule = this._getDefinitionSchedule(scriptName);
75
-
76
- if (definitionSchedule?.enabled) {
77
- return {
78
- source: 'definition',
79
- enabled: definitionSchedule.enabled,
80
- cronExpression: definitionSchedule.cronExpression,
81
- timezone: definitionSchedule.timezone || 'UTC',
82
- };
83
- }
84
-
85
- return {
86
- source: 'none',
87
- enabled: false,
88
- };
89
- }
90
-
91
57
  /**
92
58
  * Cleanup external scheduler resources
93
59
  * @private
@@ -5,10 +5,7 @@ const Boom = require('@hapi/boom');
5
5
  *
6
6
  * Application Layer - Hexagonal Architecture
7
7
  *
8
- * Resolves the effective schedule for a script following priority:
9
- * 1. Database override (runtime configuration)
10
- * 2. Definition default (code-defined schedule)
11
- * 3. None (manual execution only)
8
+ * Returns the script's database schedule, or a disabled schedule when none exists.
12
9
  */
13
10
  class GetEffectiveScheduleUseCase {
14
11
  constructor({ commands, scriptFactory }) {
@@ -19,12 +16,11 @@ class GetEffectiveScheduleUseCase {
19
16
  /**
20
17
  * Get effective schedule for a script
21
18
  * @param {string} scriptName - Name of the script
22
- * @returns {Promise<{source: 'database'|'definition'|'none', schedule: Object}>}
19
+ * @returns {Promise<{source: 'database'|'none', schedule: Object}>}
23
20
  */
24
21
  async execute(scriptName) {
25
22
  this._validateScriptExists(scriptName);
26
23
 
27
- // Priority 1: Database override
28
24
  const dbSchedule = await this.commands.getScheduleByScriptName(
29
25
  scriptName
30
26
  );
@@ -35,21 +31,6 @@ class GetEffectiveScheduleUseCase {
35
31
  };
36
32
  }
37
33
 
38
- // Priority 2: Definition default
39
- const definitionSchedule = this._getDefinitionSchedule(scriptName);
40
- if (definitionSchedule?.enabled) {
41
- return {
42
- source: 'definition',
43
- schedule: {
44
- scriptName,
45
- enabled: definitionSchedule.enabled,
46
- cronExpression: definitionSchedule.cronExpression,
47
- timezone: definitionSchedule.timezone || 'UTC',
48
- },
49
- };
50
- }
51
-
52
- // Priority 3: No schedule
53
34
  return {
54
35
  source: 'none',
55
36
  schedule: {
@@ -67,14 +48,6 @@ class GetEffectiveScheduleUseCase {
67
48
  throw Boom.notFound(`Script "${scriptName}" not found`);
68
49
  }
69
50
  }
70
-
71
- /**
72
- * @private
73
- */
74
- _getDefinitionSchedule(scriptName) {
75
- const scriptClass = this.scriptFactory.get(scriptName);
76
- return scriptClass.Definition?.schedule || null;
77
- }
78
51
  }
79
52
 
80
53
  module.exports = { GetEffectiveScheduleUseCase };
@@ -107,7 +107,6 @@ describe('Admin Script Router', () => {
107
107
  description: 'Test script',
108
108
  category: 'test',
109
109
  requireIntegrationInstance: false,
110
- schedule: null,
111
110
  });
112
111
  });
113
112
 
@@ -352,12 +351,13 @@ describe('Admin Script Router', () => {
352
351
  expect(response.body.timezone).toBe('America/New_York');
353
352
  });
354
353
 
355
- it('should return definition schedule when no database override', async () => {
354
+ it('ignores a schedule declared in the Definition (no DB override → none)', async () => {
356
355
  mockCommands.getScheduleByScriptName = jest
357
356
  .fn()
358
357
  .mockResolvedValue(null);
359
358
 
360
- // Update test script to include schedule
359
+ // A script may still carry a schedule field, but it is never used —
360
+ // only a DB override (set via PUT) activates a schedule.
361
361
  class ScheduledTestScript extends TestScript {
362
362
  static Definition = {
363
363
  ...TestScript.Definition,
@@ -376,10 +376,8 @@ describe('Admin Script Router', () => {
376
376
  );
377
377
 
378
378
  expect(response.status).toBe(200);
379
- expect(response.body.source).toBe('definition');
380
- expect(response.body.enabled).toBe(true);
381
- expect(response.body.cronExpression).toBe('0 0 * * *');
382
- expect(response.body.timezone).toBe('UTC');
379
+ expect(response.body.source).toBe('none');
380
+ expect(response.body.enabled).toBe(false);
383
381
  });
384
382
 
385
383
  it('should return none when no schedule configured', async () => {
@@ -656,13 +654,14 @@ describe('Admin Script Router', () => {
656
654
  );
657
655
  });
658
656
 
659
- it('should return definition schedule after deleting override', async () => {
657
+ it('reports no active schedule after deleting the override', async () => {
660
658
  mockCommands.deleteSchedule = jest.fn().mockResolvedValue({
661
659
  acknowledged: true,
662
660
  deletedCount: 1,
663
661
  });
664
662
 
665
- // Update test script to include schedule
663
+ // Even if the script carries a schedule field, it is ignored —
664
+ // after removing the DB override there is no active schedule.
666
665
  class ScheduledTestScript extends TestScript {
667
666
  static Definition = {
668
667
  ...TestScript.Definition,
@@ -681,8 +680,8 @@ describe('Admin Script Router', () => {
681
680
  );
682
681
 
683
682
  expect(response.status).toBe(200);
684
- expect(response.body.effectiveSchedule.source).toBe('definition');
685
- expect(response.body.effectiveSchedule.enabled).toBe(true);
683
+ expect(response.body.effectiveSchedule.source).toBe('none');
684
+ expect(response.body.effectiveSchedule.enabled).toBe(false);
686
685
  });
687
686
 
688
687
  it('should handle no schedule found', async () => {
@@ -125,7 +125,6 @@ router.get('/scripts', async (req, res) => {
125
125
  category: s.definition.display?.category || 'custom',
126
126
  requireIntegrationInstance:
127
127
  s.definition.config?.requireIntegrationInstance || false,
128
- schedule: s.definition.schedule || null,
129
128
  })),
130
129
  });
131
130
  } catch (error) {
@@ -161,7 +160,6 @@ router.get('/scripts/:scriptName', async (req, res) => {
161
160
  outputSchema: definition.outputSchema,
162
161
  config: definition.config,
163
162
  display: definition.display,
164
- schedule: definition.schedule,
165
163
  });
166
164
  } catch (error) {
167
165
  console.error('Error getting script:', error);