@dexto/tools-scheduler 1.6.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/LICENSE +44 -0
- package/dist/error-codes.cjs +44 -0
- package/dist/error-codes.d.cts +21 -0
- package/dist/error-codes.d.ts +21 -0
- package/dist/error-codes.js +20 -0
- package/dist/errors.cjs +163 -0
- package/dist/errors.d.cts +64 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +138 -0
- package/dist/executor.cjs +161 -0
- package/dist/executor.d.cts +46 -0
- package/dist/executor.d.ts +46 -0
- package/dist/executor.js +137 -0
- package/dist/index.cjs +89 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +56 -0
- package/dist/manager.cjs +461 -0
- package/dist/manager.d.cts +113 -0
- package/dist/manager.d.ts +113 -0
- package/dist/manager.js +430 -0
- package/dist/schemas.cjs +138 -0
- package/dist/schemas.d.cts +263 -0
- package/dist/schemas.d.ts +263 -0
- package/dist/schemas.js +105 -0
- package/dist/storage.cjs +249 -0
- package/dist/storage.d.cts +62 -0
- package/dist/storage.d.ts +62 -0
- package/dist/storage.js +225 -0
- package/dist/tool-provider.cjs +239 -0
- package/dist/tool-provider.d.cts +34 -0
- package/dist/tool-provider.d.ts +34 -0
- package/dist/tool-provider.js +212 -0
- package/dist/tool-types.cjs +16 -0
- package/dist/tool-types.d.cts +9 -0
- package/dist/tool-types.d.ts +9 -0
- package/dist/tool-types.js +0 -0
- package/dist/tools/create-schedule.cjs +75 -0
- package/dist/tools/create-schedule.d.cts +14 -0
- package/dist/tools/create-schedule.d.ts +14 -0
- package/dist/tools/create-schedule.js +51 -0
- package/dist/tools/delete-schedule.cjs +45 -0
- package/dist/tools/delete-schedule.d.cts +14 -0
- package/dist/tools/delete-schedule.d.ts +14 -0
- package/dist/tools/delete-schedule.js +21 -0
- package/dist/tools/get-history.cjs +63 -0
- package/dist/tools/get-history.d.cts +14 -0
- package/dist/tools/get-history.d.ts +14 -0
- package/dist/tools/get-history.js +39 -0
- package/dist/tools/get-schedule.cjs +68 -0
- package/dist/tools/get-schedule.d.cts +14 -0
- package/dist/tools/get-schedule.d.ts +14 -0
- package/dist/tools/get-schedule.js +44 -0
- package/dist/tools/list-schedules.cjs +67 -0
- package/dist/tools/list-schedules.d.cts +14 -0
- package/dist/tools/list-schedules.d.ts +14 -0
- package/dist/tools/list-schedules.js +43 -0
- package/dist/tools/trigger-schedule.cjs +56 -0
- package/dist/tools/trigger-schedule.d.cts +14 -0
- package/dist/tools/trigger-schedule.d.ts +14 -0
- package/dist/tools/trigger-schedule.js +32 -0
- package/dist/tools/update-schedule.cjs +53 -0
- package/dist/tools/update-schedule.d.cts +14 -0
- package/dist/tools/update-schedule.d.ts +14 -0
- package/dist/tools/update-schedule.js +29 -0
- package/dist/types.cjs +16 -0
- package/dist/types.d.cts +72 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +0 -0
- package/package.json +41 -0
package/dist/storage.cjs
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var storage_exports = {};
|
|
20
|
+
__export(storage_exports, {
|
|
21
|
+
ScheduleStorage: () => ScheduleStorage
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(storage_exports);
|
|
24
|
+
var import_errors = require("./errors.js");
|
|
25
|
+
const SCHEDULE_PREFIX = "schedule:";
|
|
26
|
+
const EXECUTION_LOG_PREFIX = "execution:";
|
|
27
|
+
const SCHEDULE_LIST_KEY = "scheduler:schedules";
|
|
28
|
+
class ScheduleStorage {
|
|
29
|
+
constructor(storageManager, maxExecutionHistory, logger) {
|
|
30
|
+
this.storageManager = storageManager;
|
|
31
|
+
this.maxExecutionHistory = maxExecutionHistory;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
}
|
|
34
|
+
listLock = Promise.resolve();
|
|
35
|
+
/**
|
|
36
|
+
* Save a schedule to persistent storage
|
|
37
|
+
*/
|
|
38
|
+
async saveSchedule(schedule) {
|
|
39
|
+
const key = `${SCHEDULE_PREFIX}${schedule.id}`;
|
|
40
|
+
let persisted = false;
|
|
41
|
+
try {
|
|
42
|
+
await this.storageManager.getDatabase().set(key, schedule);
|
|
43
|
+
persisted = true;
|
|
44
|
+
await this.addScheduleToList(schedule.id);
|
|
45
|
+
this.logger.debug(`Schedule ${schedule.id} saved to storage`, { name: schedule.name });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (persisted) {
|
|
48
|
+
try {
|
|
49
|
+
await this.storageManager.getDatabase().delete(key);
|
|
50
|
+
} catch (cleanupError) {
|
|
51
|
+
this.logger.error(
|
|
52
|
+
`Failed to rollback schedule ${schedule.id} after list update failure: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
throw import_errors.SchedulerError.storageWriteFailed(
|
|
57
|
+
"save schedule",
|
|
58
|
+
error instanceof Error ? error.message : String(error)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Load a schedule from storage
|
|
64
|
+
*/
|
|
65
|
+
async loadSchedule(scheduleId) {
|
|
66
|
+
try {
|
|
67
|
+
const key = `${SCHEDULE_PREFIX}${scheduleId}`;
|
|
68
|
+
const schedule = await this.storageManager.getDatabase().get(key);
|
|
69
|
+
return schedule || null;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw import_errors.SchedulerError.storageReadFailed(
|
|
72
|
+
"load schedule",
|
|
73
|
+
error instanceof Error ? error.message : String(error)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* List all schedules from storage
|
|
79
|
+
*/
|
|
80
|
+
async listSchedules() {
|
|
81
|
+
try {
|
|
82
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
83
|
+
const schedules = [];
|
|
84
|
+
for (const scheduleId of scheduleIds) {
|
|
85
|
+
const schedule = await this.loadSchedule(scheduleId);
|
|
86
|
+
if (schedule) {
|
|
87
|
+
schedules.push(schedule);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return schedules;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw import_errors.SchedulerError.storageReadFailed(
|
|
93
|
+
"list schedules",
|
|
94
|
+
error instanceof Error ? error.message : String(error)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Delete a schedule from storage
|
|
100
|
+
*/
|
|
101
|
+
async deleteSchedule(scheduleId) {
|
|
102
|
+
let removedFromList = false;
|
|
103
|
+
let deletedSchedule = false;
|
|
104
|
+
try {
|
|
105
|
+
const key = `${SCHEDULE_PREFIX}${scheduleId}`;
|
|
106
|
+
await this.removeScheduleFromList(scheduleId);
|
|
107
|
+
removedFromList = true;
|
|
108
|
+
await this.storageManager.getDatabase().delete(key);
|
|
109
|
+
deletedSchedule = true;
|
|
110
|
+
await this.deleteExecutionLogs(scheduleId);
|
|
111
|
+
this.logger.debug(`Schedule ${scheduleId} deleted from storage`);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (removedFromList && !deletedSchedule) {
|
|
114
|
+
try {
|
|
115
|
+
await this.addScheduleToList(scheduleId);
|
|
116
|
+
} catch (restoreError) {
|
|
117
|
+
this.logger.error(
|
|
118
|
+
`Failed to restore schedule ${scheduleId} to list after delete failure: ${restoreError instanceof Error ? restoreError.message : String(restoreError)}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw import_errors.SchedulerError.storageWriteFailed(
|
|
123
|
+
"delete schedule",
|
|
124
|
+
error instanceof Error ? error.message : String(error)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Save an execution log
|
|
130
|
+
*/
|
|
131
|
+
async saveExecutionLog(log) {
|
|
132
|
+
try {
|
|
133
|
+
const key = `${EXECUTION_LOG_PREFIX}${log.scheduleId}:${log.id}`;
|
|
134
|
+
await this.storageManager.getDatabase().set(key, log);
|
|
135
|
+
await this.pruneExecutionHistory(log.scheduleId);
|
|
136
|
+
this.logger.debug(`Execution log ${log.id} saved for schedule ${log.scheduleId}`, {
|
|
137
|
+
status: log.status
|
|
138
|
+
});
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw import_errors.SchedulerError.storageWriteFailed(
|
|
141
|
+
"save execution log",
|
|
142
|
+
error instanceof Error ? error.message : String(error)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get execution logs for a schedule
|
|
148
|
+
*/
|
|
149
|
+
async getExecutionLogs(scheduleId, limit) {
|
|
150
|
+
try {
|
|
151
|
+
const prefix = `${EXECUTION_LOG_PREFIX}${scheduleId}:`;
|
|
152
|
+
const keys = await this.storageManager.getDatabase().list(prefix);
|
|
153
|
+
const logs = [];
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const log = await this.storageManager.getDatabase().get(key);
|
|
156
|
+
if (log) {
|
|
157
|
+
logs.push(log);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
logs.sort((a, b) => b.triggeredAt - a.triggeredAt);
|
|
161
|
+
return limit ? logs.slice(0, limit) : logs;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw import_errors.SchedulerError.storageReadFailed(
|
|
164
|
+
"get execution logs",
|
|
165
|
+
error instanceof Error ? error.message : String(error)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Delete all execution logs for a schedule
|
|
171
|
+
*/
|
|
172
|
+
async deleteExecutionLogs(scheduleId) {
|
|
173
|
+
try {
|
|
174
|
+
const prefix = `${EXECUTION_LOG_PREFIX}${scheduleId}:`;
|
|
175
|
+
const keys = await this.storageManager.getDatabase().list(prefix);
|
|
176
|
+
for (const key of keys) {
|
|
177
|
+
await this.storageManager.getDatabase().delete(key);
|
|
178
|
+
}
|
|
179
|
+
this.logger.debug(`Execution logs deleted for schedule ${scheduleId}`);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logger.error(
|
|
182
|
+
`Failed to delete execution logs for schedule ${scheduleId}: ${error instanceof Error ? error.message : String(error)}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Prune execution history to maintain limit
|
|
188
|
+
*/
|
|
189
|
+
async pruneExecutionHistory(scheduleId) {
|
|
190
|
+
try {
|
|
191
|
+
const logs = await this.getExecutionLogs(scheduleId);
|
|
192
|
+
if (logs.length > this.maxExecutionHistory) {
|
|
193
|
+
const logsToDelete = logs.slice(this.maxExecutionHistory);
|
|
194
|
+
for (const log of logsToDelete) {
|
|
195
|
+
const key = `${EXECUTION_LOG_PREFIX}${scheduleId}:${log.id}`;
|
|
196
|
+
await this.storageManager.getDatabase().delete(key);
|
|
197
|
+
}
|
|
198
|
+
this.logger.debug(
|
|
199
|
+
`Pruned ${logsToDelete.length} old execution logs for schedule ${scheduleId}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
this.logger.error(
|
|
204
|
+
`Failed to prune execution history for schedule ${scheduleId}: ${error instanceof Error ? error.message : String(error)}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Add schedule ID to master list
|
|
210
|
+
*/
|
|
211
|
+
async addScheduleToList(scheduleId) {
|
|
212
|
+
await this.withListLock(async () => {
|
|
213
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
214
|
+
if (!scheduleIds.includes(scheduleId)) {
|
|
215
|
+
scheduleIds.push(scheduleId);
|
|
216
|
+
await this.storageManager.getDatabase().set(SCHEDULE_LIST_KEY, scheduleIds);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Remove schedule ID from master list
|
|
222
|
+
*/
|
|
223
|
+
async removeScheduleFromList(scheduleId) {
|
|
224
|
+
await this.withListLock(async () => {
|
|
225
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
226
|
+
const filtered = scheduleIds.filter((id) => id !== scheduleId);
|
|
227
|
+
await this.storageManager.getDatabase().set(SCHEDULE_LIST_KEY, filtered);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async withListLock(handler) {
|
|
231
|
+
const previous = this.listLock;
|
|
232
|
+
let release;
|
|
233
|
+
this.listLock = new Promise((resolve) => {
|
|
234
|
+
release = resolve;
|
|
235
|
+
});
|
|
236
|
+
await previous;
|
|
237
|
+
try {
|
|
238
|
+
return await handler();
|
|
239
|
+
} finally {
|
|
240
|
+
if (release) {
|
|
241
|
+
release();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
247
|
+
0 && (module.exports = {
|
|
248
|
+
ScheduleStorage
|
|
249
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StorageManager, Logger } from '@dexto/core';
|
|
2
|
+
import { Schedule, ExecutionLog } from './types.cjs';
|
|
3
|
+
import './schemas.cjs';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Storage layer for schedules and execution logs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Storage layer for scheduler persistence
|
|
12
|
+
*/
|
|
13
|
+
declare class ScheduleStorage {
|
|
14
|
+
private storageManager;
|
|
15
|
+
private maxExecutionHistory;
|
|
16
|
+
private logger;
|
|
17
|
+
private listLock;
|
|
18
|
+
constructor(storageManager: StorageManager, maxExecutionHistory: number, logger: Logger);
|
|
19
|
+
/**
|
|
20
|
+
* Save a schedule to persistent storage
|
|
21
|
+
*/
|
|
22
|
+
saveSchedule(schedule: Schedule): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Load a schedule from storage
|
|
25
|
+
*/
|
|
26
|
+
loadSchedule(scheduleId: string): Promise<Schedule | null>;
|
|
27
|
+
/**
|
|
28
|
+
* List all schedules from storage
|
|
29
|
+
*/
|
|
30
|
+
listSchedules(): Promise<Schedule[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete a schedule from storage
|
|
33
|
+
*/
|
|
34
|
+
deleteSchedule(scheduleId: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Save an execution log
|
|
37
|
+
*/
|
|
38
|
+
saveExecutionLog(log: ExecutionLog): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get execution logs for a schedule
|
|
41
|
+
*/
|
|
42
|
+
getExecutionLogs(scheduleId: string, limit?: number): Promise<ExecutionLog[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete all execution logs for a schedule
|
|
45
|
+
*/
|
|
46
|
+
private deleteExecutionLogs;
|
|
47
|
+
/**
|
|
48
|
+
* Prune execution history to maintain limit
|
|
49
|
+
*/
|
|
50
|
+
private pruneExecutionHistory;
|
|
51
|
+
/**
|
|
52
|
+
* Add schedule ID to master list
|
|
53
|
+
*/
|
|
54
|
+
private addScheduleToList;
|
|
55
|
+
/**
|
|
56
|
+
* Remove schedule ID from master list
|
|
57
|
+
*/
|
|
58
|
+
private removeScheduleFromList;
|
|
59
|
+
private withListLock;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { ScheduleStorage };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { StorageManager, Logger } from '@dexto/core';
|
|
2
|
+
import { Schedule, ExecutionLog } from './types.js';
|
|
3
|
+
import './schemas.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Storage layer for schedules and execution logs
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Storage layer for scheduler persistence
|
|
12
|
+
*/
|
|
13
|
+
declare class ScheduleStorage {
|
|
14
|
+
private storageManager;
|
|
15
|
+
private maxExecutionHistory;
|
|
16
|
+
private logger;
|
|
17
|
+
private listLock;
|
|
18
|
+
constructor(storageManager: StorageManager, maxExecutionHistory: number, logger: Logger);
|
|
19
|
+
/**
|
|
20
|
+
* Save a schedule to persistent storage
|
|
21
|
+
*/
|
|
22
|
+
saveSchedule(schedule: Schedule): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Load a schedule from storage
|
|
25
|
+
*/
|
|
26
|
+
loadSchedule(scheduleId: string): Promise<Schedule | null>;
|
|
27
|
+
/**
|
|
28
|
+
* List all schedules from storage
|
|
29
|
+
*/
|
|
30
|
+
listSchedules(): Promise<Schedule[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete a schedule from storage
|
|
33
|
+
*/
|
|
34
|
+
deleteSchedule(scheduleId: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Save an execution log
|
|
37
|
+
*/
|
|
38
|
+
saveExecutionLog(log: ExecutionLog): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get execution logs for a schedule
|
|
41
|
+
*/
|
|
42
|
+
getExecutionLogs(scheduleId: string, limit?: number): Promise<ExecutionLog[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Delete all execution logs for a schedule
|
|
45
|
+
*/
|
|
46
|
+
private deleteExecutionLogs;
|
|
47
|
+
/**
|
|
48
|
+
* Prune execution history to maintain limit
|
|
49
|
+
*/
|
|
50
|
+
private pruneExecutionHistory;
|
|
51
|
+
/**
|
|
52
|
+
* Add schedule ID to master list
|
|
53
|
+
*/
|
|
54
|
+
private addScheduleToList;
|
|
55
|
+
/**
|
|
56
|
+
* Remove schedule ID from master list
|
|
57
|
+
*/
|
|
58
|
+
private removeScheduleFromList;
|
|
59
|
+
private withListLock;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { ScheduleStorage };
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { SchedulerError } from "./errors.js";
|
|
2
|
+
const SCHEDULE_PREFIX = "schedule:";
|
|
3
|
+
const EXECUTION_LOG_PREFIX = "execution:";
|
|
4
|
+
const SCHEDULE_LIST_KEY = "scheduler:schedules";
|
|
5
|
+
class ScheduleStorage {
|
|
6
|
+
constructor(storageManager, maxExecutionHistory, logger) {
|
|
7
|
+
this.storageManager = storageManager;
|
|
8
|
+
this.maxExecutionHistory = maxExecutionHistory;
|
|
9
|
+
this.logger = logger;
|
|
10
|
+
}
|
|
11
|
+
listLock = Promise.resolve();
|
|
12
|
+
/**
|
|
13
|
+
* Save a schedule to persistent storage
|
|
14
|
+
*/
|
|
15
|
+
async saveSchedule(schedule) {
|
|
16
|
+
const key = `${SCHEDULE_PREFIX}${schedule.id}`;
|
|
17
|
+
let persisted = false;
|
|
18
|
+
try {
|
|
19
|
+
await this.storageManager.getDatabase().set(key, schedule);
|
|
20
|
+
persisted = true;
|
|
21
|
+
await this.addScheduleToList(schedule.id);
|
|
22
|
+
this.logger.debug(`Schedule ${schedule.id} saved to storage`, { name: schedule.name });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (persisted) {
|
|
25
|
+
try {
|
|
26
|
+
await this.storageManager.getDatabase().delete(key);
|
|
27
|
+
} catch (cleanupError) {
|
|
28
|
+
this.logger.error(
|
|
29
|
+
`Failed to rollback schedule ${schedule.id} after list update failure: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw SchedulerError.storageWriteFailed(
|
|
34
|
+
"save schedule",
|
|
35
|
+
error instanceof Error ? error.message : String(error)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Load a schedule from storage
|
|
41
|
+
*/
|
|
42
|
+
async loadSchedule(scheduleId) {
|
|
43
|
+
try {
|
|
44
|
+
const key = `${SCHEDULE_PREFIX}${scheduleId}`;
|
|
45
|
+
const schedule = await this.storageManager.getDatabase().get(key);
|
|
46
|
+
return schedule || null;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
throw SchedulerError.storageReadFailed(
|
|
49
|
+
"load schedule",
|
|
50
|
+
error instanceof Error ? error.message : String(error)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* List all schedules from storage
|
|
56
|
+
*/
|
|
57
|
+
async listSchedules() {
|
|
58
|
+
try {
|
|
59
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
60
|
+
const schedules = [];
|
|
61
|
+
for (const scheduleId of scheduleIds) {
|
|
62
|
+
const schedule = await this.loadSchedule(scheduleId);
|
|
63
|
+
if (schedule) {
|
|
64
|
+
schedules.push(schedule);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return schedules;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw SchedulerError.storageReadFailed(
|
|
70
|
+
"list schedules",
|
|
71
|
+
error instanceof Error ? error.message : String(error)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Delete a schedule from storage
|
|
77
|
+
*/
|
|
78
|
+
async deleteSchedule(scheduleId) {
|
|
79
|
+
let removedFromList = false;
|
|
80
|
+
let deletedSchedule = false;
|
|
81
|
+
try {
|
|
82
|
+
const key = `${SCHEDULE_PREFIX}${scheduleId}`;
|
|
83
|
+
await this.removeScheduleFromList(scheduleId);
|
|
84
|
+
removedFromList = true;
|
|
85
|
+
await this.storageManager.getDatabase().delete(key);
|
|
86
|
+
deletedSchedule = true;
|
|
87
|
+
await this.deleteExecutionLogs(scheduleId);
|
|
88
|
+
this.logger.debug(`Schedule ${scheduleId} deleted from storage`);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (removedFromList && !deletedSchedule) {
|
|
91
|
+
try {
|
|
92
|
+
await this.addScheduleToList(scheduleId);
|
|
93
|
+
} catch (restoreError) {
|
|
94
|
+
this.logger.error(
|
|
95
|
+
`Failed to restore schedule ${scheduleId} to list after delete failure: ${restoreError instanceof Error ? restoreError.message : String(restoreError)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw SchedulerError.storageWriteFailed(
|
|
100
|
+
"delete schedule",
|
|
101
|
+
error instanceof Error ? error.message : String(error)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Save an execution log
|
|
107
|
+
*/
|
|
108
|
+
async saveExecutionLog(log) {
|
|
109
|
+
try {
|
|
110
|
+
const key = `${EXECUTION_LOG_PREFIX}${log.scheduleId}:${log.id}`;
|
|
111
|
+
await this.storageManager.getDatabase().set(key, log);
|
|
112
|
+
await this.pruneExecutionHistory(log.scheduleId);
|
|
113
|
+
this.logger.debug(`Execution log ${log.id} saved for schedule ${log.scheduleId}`, {
|
|
114
|
+
status: log.status
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
throw SchedulerError.storageWriteFailed(
|
|
118
|
+
"save execution log",
|
|
119
|
+
error instanceof Error ? error.message : String(error)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get execution logs for a schedule
|
|
125
|
+
*/
|
|
126
|
+
async getExecutionLogs(scheduleId, limit) {
|
|
127
|
+
try {
|
|
128
|
+
const prefix = `${EXECUTION_LOG_PREFIX}${scheduleId}:`;
|
|
129
|
+
const keys = await this.storageManager.getDatabase().list(prefix);
|
|
130
|
+
const logs = [];
|
|
131
|
+
for (const key of keys) {
|
|
132
|
+
const log = await this.storageManager.getDatabase().get(key);
|
|
133
|
+
if (log) {
|
|
134
|
+
logs.push(log);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
logs.sort((a, b) => b.triggeredAt - a.triggeredAt);
|
|
138
|
+
return limit ? logs.slice(0, limit) : logs;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw SchedulerError.storageReadFailed(
|
|
141
|
+
"get execution logs",
|
|
142
|
+
error instanceof Error ? error.message : String(error)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Delete all execution logs for a schedule
|
|
148
|
+
*/
|
|
149
|
+
async deleteExecutionLogs(scheduleId) {
|
|
150
|
+
try {
|
|
151
|
+
const prefix = `${EXECUTION_LOG_PREFIX}${scheduleId}:`;
|
|
152
|
+
const keys = await this.storageManager.getDatabase().list(prefix);
|
|
153
|
+
for (const key of keys) {
|
|
154
|
+
await this.storageManager.getDatabase().delete(key);
|
|
155
|
+
}
|
|
156
|
+
this.logger.debug(`Execution logs deleted for schedule ${scheduleId}`);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.logger.error(
|
|
159
|
+
`Failed to delete execution logs for schedule ${scheduleId}: ${error instanceof Error ? error.message : String(error)}`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Prune execution history to maintain limit
|
|
165
|
+
*/
|
|
166
|
+
async pruneExecutionHistory(scheduleId) {
|
|
167
|
+
try {
|
|
168
|
+
const logs = await this.getExecutionLogs(scheduleId);
|
|
169
|
+
if (logs.length > this.maxExecutionHistory) {
|
|
170
|
+
const logsToDelete = logs.slice(this.maxExecutionHistory);
|
|
171
|
+
for (const log of logsToDelete) {
|
|
172
|
+
const key = `${EXECUTION_LOG_PREFIX}${scheduleId}:${log.id}`;
|
|
173
|
+
await this.storageManager.getDatabase().delete(key);
|
|
174
|
+
}
|
|
175
|
+
this.logger.debug(
|
|
176
|
+
`Pruned ${logsToDelete.length} old execution logs for schedule ${scheduleId}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
this.logger.error(
|
|
181
|
+
`Failed to prune execution history for schedule ${scheduleId}: ${error instanceof Error ? error.message : String(error)}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Add schedule ID to master list
|
|
187
|
+
*/
|
|
188
|
+
async addScheduleToList(scheduleId) {
|
|
189
|
+
await this.withListLock(async () => {
|
|
190
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
191
|
+
if (!scheduleIds.includes(scheduleId)) {
|
|
192
|
+
scheduleIds.push(scheduleId);
|
|
193
|
+
await this.storageManager.getDatabase().set(SCHEDULE_LIST_KEY, scheduleIds);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Remove schedule ID from master list
|
|
199
|
+
*/
|
|
200
|
+
async removeScheduleFromList(scheduleId) {
|
|
201
|
+
await this.withListLock(async () => {
|
|
202
|
+
const scheduleIds = await this.storageManager.getDatabase().get(SCHEDULE_LIST_KEY) || [];
|
|
203
|
+
const filtered = scheduleIds.filter((id) => id !== scheduleId);
|
|
204
|
+
await this.storageManager.getDatabase().set(SCHEDULE_LIST_KEY, filtered);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async withListLock(handler) {
|
|
208
|
+
const previous = this.listLock;
|
|
209
|
+
let release;
|
|
210
|
+
this.listLock = new Promise((resolve) => {
|
|
211
|
+
release = resolve;
|
|
212
|
+
});
|
|
213
|
+
await previous;
|
|
214
|
+
try {
|
|
215
|
+
return await handler();
|
|
216
|
+
} finally {
|
|
217
|
+
if (release) {
|
|
218
|
+
release();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export {
|
|
224
|
+
ScheduleStorage
|
|
225
|
+
};
|