@friggframework/admin-scripts 2.0.0--canary.517.ff03f2c.0 → 2.0.0--canary.517.542a9ec.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,18 +1,19 @@
1
1
  {
2
2
  "name": "@friggframework/admin-scripts",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.517.ff03f2c.0",
4
+ "version": "2.0.0--canary.517.542a9ec.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.ff03f2c.0",
8
+ "@friggframework/core": "2.0.0--canary.517.542a9ec.0",
9
+ "@hapi/boom": "^10.0.1",
9
10
  "express": "^4.18.2",
10
11
  "serverless-http": "^3.2.0"
11
12
  },
12
13
  "devDependencies": {
13
- "@friggframework/eslint-config": "2.0.0--canary.517.ff03f2c.0",
14
- "@friggframework/prettier-config": "2.0.0--canary.517.ff03f2c.0",
15
- "@friggframework/test": "2.0.0--canary.517.ff03f2c.0",
14
+ "@friggframework/eslint-config": "2.0.0--canary.517.542a9ec.0",
15
+ "@friggframework/prettier-config": "2.0.0--canary.517.542a9ec.0",
16
+ "@friggframework/test": "2.0.0--canary.517.542a9ec.0",
16
17
  "eslint": "^8.22.0",
17
18
  "jest": "^29.7.0",
18
19
  "prettier": "^2.7.1",
@@ -43,5 +44,5 @@
43
44
  "maintenance",
44
45
  "operations"
45
46
  ],
46
- "gitHead": "ff03f2c9204a318789ed105c09cc9d44deeae6ec"
47
+ "gitHead": "542a9ec92adca85b29dd727125bbeb5d7e688dbc"
47
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,10 +8,8 @@ class AdminScriptBase {
8
8
  inputSchema: null,
9
9
  outputSchema: null,
10
10
 
11
- schedule: {
12
- enabled: false,
13
- cronExpression: null,
14
- },
11
+ // Scheduling is not declared here — a script is a capability. Admins
12
+ // activate a schedule at runtime via PUT /admin/scripts/:name/schedule.
15
13
 
16
14
  config: {
17
15
  timeout: 300000,
@@ -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({
@@ -170,7 +145,7 @@ describe('DeleteScheduleUseCase', () => {
170
145
  try {
171
146
  await useCase.execute('non-existent');
172
147
  } catch (error) {
173
- expect(error.code).toBe('SCRIPT_NOT_FOUND');
148
+ expect(error.output.statusCode).toBe(404);
174
149
  }
175
150
  });
176
151
  });
@@ -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
 
@@ -112,7 +79,7 @@ describe('GetEffectiveScheduleUseCase', () => {
112
79
  try {
113
80
  await useCase.execute('non-existent');
114
81
  } catch (error) {
115
- expect(error.code).toBe('SCRIPT_NOT_FOUND');
82
+ expect(error.output.statusCode).toBe(404);
116
83
  }
117
84
  });
118
85
  });
@@ -153,7 +153,7 @@ describe('UpsertScheduleUseCase', () => {
153
153
  try {
154
154
  await useCase.execute('non-existent', { enabled: true });
155
155
  } catch (error) {
156
- expect(error.code).toBe('SCRIPT_NOT_FOUND');
156
+ expect(error.output.statusCode).toBe(404);
157
157
  }
158
158
  });
159
159
 
@@ -167,7 +167,7 @@ describe('UpsertScheduleUseCase', () => {
167
167
  try {
168
168
  await useCase.execute('test-script', { enabled: 'yes' });
169
169
  } catch (error) {
170
- expect(error.code).toBe('INVALID_INPUT');
170
+ expect(error.output.statusCode).toBe(400);
171
171
  }
172
172
  });
173
173
 
@@ -183,7 +183,7 @@ describe('UpsertScheduleUseCase', () => {
183
183
  try {
184
184
  await useCase.execute('test-script', { enabled: true });
185
185
  } catch (error) {
186
- expect(error.code).toBe('INVALID_INPUT');
186
+ expect(error.output.statusCode).toBe(400);
187
187
  }
188
188
  });
189
189
 
@@ -1,10 +1,13 @@
1
+ const Boom = require('@hapi/boom');
2
+
1
3
  /**
2
4
  * Delete Schedule Use Case
3
5
  *
4
6
  * Application Layer - Hexagonal Architecture
5
7
  *
6
8
  * Deletes a schedule override and cleans up external scheduler resources.
7
- * Returns the effective schedule after deletion (may fall back to definition).
9
+ * After deletion the script has no active schedule schedules are never
10
+ * derived from the Definition; an admin re-activates one via PUT /schedule.
8
11
  */
9
12
  class DeleteScheduleUseCase {
10
13
  constructor({ commands, schedulerAdapter, scriptFactory }) {
@@ -30,9 +33,9 @@ class DeleteScheduleUseCase {
30
33
  deleteResult.deleted?.externalScheduleId
31
34
  );
32
35
 
33
- // Determine effective schedule after deletion
34
- const effectiveSchedule =
35
- this._getEffectiveScheduleAfterDeletion(scriptName);
36
+ // Nothing derives a schedule from the Definition, so once the DB
37
+ // override is removed the script has no active schedule.
38
+ const effectiveSchedule = { source: 'none', enabled: false };
36
39
 
37
40
  return {
38
41
  success: true,
@@ -51,41 +54,8 @@ class DeleteScheduleUseCase {
51
54
  */
52
55
  _validateScriptExists(scriptName) {
53
56
  if (!this.scriptFactory.has(scriptName)) {
54
- const error = new Error(`Script "${scriptName}" not found`);
55
- error.code = 'SCRIPT_NOT_FOUND';
56
- throw error;
57
- }
58
- }
59
-
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
- };
57
+ throw Boom.notFound(`Script "${scriptName}" not found`);
83
58
  }
84
-
85
- return {
86
- source: 'none',
87
- enabled: false,
88
- };
89
59
  }
90
60
 
91
61
  /**
@@ -1,12 +1,16 @@
1
+ const Boom = require('@hapi/boom');
2
+
1
3
  /**
2
4
  * Get Effective Schedule Use Case
3
5
  *
4
6
  * Application Layer - Hexagonal Architecture
5
7
  *
6
- * Resolves the effective schedule for a script following priority:
7
- * 1. Database override (runtime configuration)
8
- * 2. Definition default (code-defined schedule)
9
- * 3. None (manual execution only)
8
+ * Resolves the effective schedule for a script:
9
+ * 1. Database override (activated at runtime via PUT /schedule)
10
+ * 2. None (script is declared but not actively scheduled)
11
+ *
12
+ * Schedules are never derived from the script Definition — a script declares
13
+ * a capability; an admin activates a schedule explicitly via the API.
10
14
  */
11
15
  class GetEffectiveScheduleUseCase {
12
16
  constructor({ commands, scriptFactory }) {
@@ -17,7 +21,7 @@ class GetEffectiveScheduleUseCase {
17
21
  /**
18
22
  * Get effective schedule for a script
19
23
  * @param {string} scriptName - Name of the script
20
- * @returns {Promise<{source: 'database'|'definition'|'none', schedule: Object}>}
24
+ * @returns {Promise<{source: 'database'|'none', schedule: Object}>}
21
25
  */
22
26
  async execute(scriptName) {
23
27
  this._validateScriptExists(scriptName);
@@ -33,21 +37,8 @@ class GetEffectiveScheduleUseCase {
33
37
  };
34
38
  }
35
39
 
36
- // Priority 2: Definition default
37
- const definitionSchedule = this._getDefinitionSchedule(scriptName);
38
- if (definitionSchedule?.enabled) {
39
- return {
40
- source: 'definition',
41
- schedule: {
42
- scriptName,
43
- enabled: definitionSchedule.enabled,
44
- cronExpression: definitionSchedule.cronExpression,
45
- timezone: definitionSchedule.timezone || 'UTC',
46
- },
47
- };
48
- }
49
-
50
- // Priority 3: No schedule
40
+ // No database override: the script is declared but not actively
41
+ // scheduled. Admins activate scheduling explicitly via PUT /schedule.
51
42
  return {
52
43
  source: 'none',
53
44
  schedule: {
@@ -62,19 +53,9 @@ class GetEffectiveScheduleUseCase {
62
53
  */
63
54
  _validateScriptExists(scriptName) {
64
55
  if (!this.scriptFactory.has(scriptName)) {
65
- const error = new Error(`Script "${scriptName}" not found`);
66
- error.code = 'SCRIPT_NOT_FOUND';
67
- throw error;
56
+ throw Boom.notFound(`Script "${scriptName}" not found`);
68
57
  }
69
58
  }
70
-
71
- /**
72
- * @private
73
- */
74
- _getDefinitionSchedule(scriptName) {
75
- const scriptClass = this.scriptFactory.get(scriptName);
76
- return scriptClass.Definition?.schedule || null;
77
- }
78
59
  }
79
60
 
80
61
  module.exports = { GetEffectiveScheduleUseCase };
@@ -1,3 +1,5 @@
1
+ const Boom = require('@hapi/boom');
2
+
1
3
  /**
2
4
  * Upsert Schedule Use Case
3
5
  *
@@ -65,9 +67,7 @@ class UpsertScheduleUseCase {
65
67
  */
66
68
  _validateScriptExists(scriptName) {
67
69
  if (!this.scriptFactory.has(scriptName)) {
68
- const error = new Error(`Script "${scriptName}" not found`);
69
- error.code = 'SCRIPT_NOT_FOUND';
70
- throw error;
70
+ throw Boom.notFound(`Script "${scriptName}" not found`);
71
71
  }
72
72
  }
73
73
 
@@ -76,17 +76,13 @@ class UpsertScheduleUseCase {
76
76
  */
77
77
  _validateInput(enabled, cronExpression) {
78
78
  if (typeof enabled !== 'boolean') {
79
- const error = new Error('enabled must be a boolean');
80
- error.code = 'INVALID_INPUT';
81
- throw error;
79
+ throw Boom.badRequest('enabled must be a boolean');
82
80
  }
83
81
 
84
82
  if (enabled && !cronExpression) {
85
- const error = new Error(
83
+ throw Boom.badRequest(
86
84
  'cronExpression is required when enabled is true'
87
85
  );
88
- error.code = 'INVALID_INPUT';
89
- throw error;
90
86
  }
91
87
  }
92
88
 
@@ -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 () => {
@@ -404,7 +402,7 @@ describe('Admin Script Router', () => {
404
402
  );
405
403
 
406
404
  expect(response.status).toBe(404);
407
- expect(response.body.code).toBe('SCRIPT_NOT_FOUND');
405
+ expect(response.body.error).toMatch(/not found/i);
408
406
  });
409
407
  });
410
408
 
@@ -481,7 +479,6 @@ describe('Admin Script Router', () => {
481
479
  });
482
480
 
483
481
  expect(response.status).toBe(400);
484
- expect(response.body.code).toBe('INVALID_INPUT');
485
482
  expect(response.body.error).toContain('enabled');
486
483
  });
487
484
 
@@ -493,7 +490,6 @@ describe('Admin Script Router', () => {
493
490
  });
494
491
 
495
492
  expect(response.status).toBe(400);
496
- expect(response.body.code).toBe('INVALID_INPUT');
497
493
  expect(response.body.error).toContain('cronExpression');
498
494
  });
499
495
 
@@ -508,7 +504,7 @@ describe('Admin Script Router', () => {
508
504
  });
509
505
 
510
506
  expect(response.status).toBe(404);
511
- expect(response.body.code).toBe('SCRIPT_NOT_FOUND');
507
+ expect(response.body.error).toMatch(/not found/i);
512
508
  });
513
509
 
514
510
  it('should provision EventBridge schedule when enabled', async () => {
@@ -658,13 +654,14 @@ describe('Admin Script Router', () => {
658
654
  );
659
655
  });
660
656
 
661
- it('should return definition schedule after deleting override', async () => {
657
+ it('reports no active schedule after deleting the override', async () => {
662
658
  mockCommands.deleteSchedule = jest.fn().mockResolvedValue({
663
659
  acknowledged: true,
664
660
  deletedCount: 1,
665
661
  });
666
662
 
667
- // 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.
668
665
  class ScheduledTestScript extends TestScript {
669
666
  static Definition = {
670
667
  ...TestScript.Definition,
@@ -683,8 +680,8 @@ describe('Admin Script Router', () => {
683
680
  );
684
681
 
685
682
  expect(response.status).toBe(200);
686
- expect(response.body.effectiveSchedule.source).toBe('definition');
687
- expect(response.body.effectiveSchedule.enabled).toBe(true);
683
+ expect(response.body.effectiveSchedule.source).toBe('none');
684
+ expect(response.body.effectiveSchedule.enabled).toBe(false);
688
685
  });
689
686
 
690
687
  it('should handle no schedule found', async () => {
@@ -712,7 +709,7 @@ describe('Admin Script Router', () => {
712
709
  );
713
710
 
714
711
  expect(response.status).toBe(404);
715
- expect(response.body.code).toBe('SCRIPT_NOT_FOUND');
712
+ expect(response.body.error).toMatch(/not found/i);
716
713
  });
717
714
 
718
715
  it('should delete EventBridge schedule when external rule exists', async () => {
@@ -1,5 +1,6 @@
1
1
  const express = require('express');
2
2
  const serverless = require('serverless-http');
3
+ const Boom = require('@hapi/boom');
3
4
  const { validateAdminApiKey } = require('./admin-auth-middleware');
4
5
  const { getScriptFactory } = require('../application/script-factory');
5
6
  const { createScriptRunner } = require('../application/script-runner');
@@ -26,13 +27,27 @@ const router = express.Router();
26
27
  // Apply auth middleware to all admin routes
27
28
  router.use(validateAdminApiKey);
28
29
 
29
- // Register the host app's admin scripts (and built-ins) before handling requests.
30
+ // Register the host app's admin scripts before handling requests.
30
31
  // Memoized, so this only does work on the first request per process.
31
32
  router.use((_req, _res, next) => {
32
33
  bootstrapAdminScripts();
33
34
  next();
34
35
  });
35
36
 
37
+ /**
38
+ * Translate a thrown error into an HTTP response. Boom errors (thrown by the
39
+ * schedule use cases) carry their own status code; anything else is an
40
+ * unexpected 500. Mirrors the framework's app-handler-helpers convention.
41
+ * @private
42
+ */
43
+ function sendError(res, error, fallbackMessage) {
44
+ if (error.isBoom) {
45
+ return res.status(error.output.statusCode).json({ error: error.message });
46
+ }
47
+ console.error(fallbackMessage, error);
48
+ return res.status(500).json({ error: fallbackMessage });
49
+ }
50
+
36
51
  /**
37
52
  * Build audit metadata for an execution from the request.
38
53
  * @private
@@ -62,11 +77,9 @@ function createScheduleUseCases() {
62
77
  process.env.SCHEDULER_PROVIDER ||
63
78
  (process.env.AWS_LAMBDA_FUNCTION_NAME ? null : 'local');
64
79
  if (!schedulerType) {
65
- const error = new Error(
80
+ throw Boom.serverUnavailable(
66
81
  'SCHEDULER_PROVIDER is not configured. Set it (e.g. "aws") via appDefinition.admin.enableScheduling.'
67
82
  );
68
- error.code = 'SCHEDULER_NOT_CONFIGURED';
69
- throw error;
70
83
  }
71
84
 
72
85
  const schedulerAdapter = createSchedulerAdapter({
@@ -112,7 +125,6 @@ router.get('/scripts', async (req, res) => {
112
125
  category: s.definition.display?.category || 'custom',
113
126
  requireIntegrationInstance:
114
127
  s.definition.config?.requireIntegrationInstance || false,
115
- schedule: s.definition.schedule || null,
116
128
  })),
117
129
  });
118
130
  } catch (error) {
@@ -148,7 +160,6 @@ router.get('/scripts/:scriptName', async (req, res) => {
148
160
  outputSchema: definition.outputSchema,
149
161
  config: definition.config,
150
162
  display: definition.display,
151
- schedule: definition.schedule,
152
163
  });
153
164
  } catch (error) {
154
165
  console.error('Error getting script:', error);
@@ -354,19 +365,7 @@ router.get('/scripts/:scriptName/schedule', async (req, res) => {
354
365
  ...result.schedule,
355
366
  });
356
367
  } catch (error) {
357
- if (error.code === 'SCRIPT_NOT_FOUND') {
358
- return res.status(404).json({
359
- error: error.message,
360
- code: error.code,
361
- });
362
- }
363
- if (error.code === 'SCHEDULER_NOT_CONFIGURED') {
364
- return res
365
- .status(503)
366
- .json({ error: error.message, code: error.code });
367
- }
368
- console.error('Error getting schedule:', error);
369
- res.status(500).json({ error: 'Failed to get schedule' });
368
+ return sendError(res, error, 'Failed to get schedule');
370
369
  }
371
370
  });
372
371
 
@@ -397,25 +396,7 @@ router.put('/scripts/:scriptName/schedule', async (req, res) => {
397
396
  }),
398
397
  });
399
398
  } catch (error) {
400
- if (error.code === 'SCRIPT_NOT_FOUND') {
401
- return res.status(404).json({
402
- error: error.message,
403
- code: error.code,
404
- });
405
- }
406
- if (error.code === 'INVALID_INPUT') {
407
- return res.status(400).json({
408
- error: error.message,
409
- code: error.code,
410
- });
411
- }
412
- if (error.code === 'SCHEDULER_NOT_CONFIGURED') {
413
- return res
414
- .status(503)
415
- .json({ error: error.message, code: error.code });
416
- }
417
- console.error('Error updating schedule:', error);
418
- res.status(500).json({ error: 'Failed to update schedule' });
399
+ return sendError(res, error, 'Failed to update schedule');
419
400
  }
420
401
  });
421
402
 
@@ -432,19 +413,7 @@ router.delete('/scripts/:scriptName/schedule', async (req, res) => {
432
413
 
433
414
  res.json(result);
434
415
  } catch (error) {
435
- if (error.code === 'SCRIPT_NOT_FOUND') {
436
- return res.status(404).json({
437
- error: error.message,
438
- code: error.code,
439
- });
440
- }
441
- if (error.code === 'SCHEDULER_NOT_CONFIGURED') {
442
- return res
443
- .status(503)
444
- .json({ error: error.message, code: error.code });
445
- }
446
- console.error('Error deleting schedule:', error);
447
- res.status(500).json({ error: 'Failed to delete schedule' });
416
+ return sendError(res, error, 'Failed to delete schedule');
448
417
  }
449
418
  });
450
419