@hachej/boring-automation 0.1.71 → 0.1.80
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 +21 -0
- package/README.md +14 -1
- package/dist/front/index.d.ts +29 -2
- package/dist/front/index.js +954 -26
- package/dist/server/index.d.ts +122 -13
- package/dist/server/index.js +780 -60
- package/dist/shared/index.d.ts +90 -54
- package/dist/shared/index.js +124 -18
- package/package.json +17 -15
package/dist/shared/index.d.ts
CHANGED
|
@@ -6,14 +6,22 @@ declare const BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
|
6
6
|
|
|
7
7
|
declare const BORING_AUTOMATION_ERROR_CODES: {
|
|
8
8
|
readonly INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY";
|
|
9
|
+
readonly INVALID_CRON: "BORING_AUTOMATION_INVALID_CRON";
|
|
10
|
+
readonly INVALID_TIMEZONE: "BORING_AUTOMATION_INVALID_TIMEZONE";
|
|
11
|
+
readonly INVALID_MODEL: "BORING_AUTOMATION_INVALID_MODEL";
|
|
9
12
|
readonly AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND";
|
|
10
13
|
readonly RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND";
|
|
14
|
+
readonly RUN_ALREADY_ACTIVE: "BORING_AUTOMATION_RUN_ALREADY_ACTIVE";
|
|
15
|
+
readonly RUN_ALREADY_RECORDED: "BORING_AUTOMATION_RUN_ALREADY_RECORDED";
|
|
16
|
+
readonly RUN_EXECUTOR_UNAVAILABLE: "BORING_AUTOMATION_RUN_EXECUTOR_UNAVAILABLE";
|
|
17
|
+
readonly TRIGGER_FORBIDDEN: "BORING_AUTOMATION_TRIGGER_FORBIDDEN";
|
|
18
|
+
readonly RUN_FAILED: "BORING_AUTOMATION_RUN_FAILED";
|
|
11
19
|
};
|
|
12
20
|
type BoringAutomationErrorCode = typeof BORING_AUTOMATION_ERROR_CODES[keyof typeof BORING_AUTOMATION_ERROR_CODES];
|
|
13
21
|
|
|
14
22
|
declare const AutomationRunStatusSchema: z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>;
|
|
15
23
|
declare const AutomationRunTriggerSchema: z.ZodEnum<["manual", "scheduled"]>;
|
|
16
|
-
declare const AutomationCreateSchema: z.ZodObject<{
|
|
24
|
+
declare const AutomationCreateSchema: z.ZodEffects<z.ZodObject<{
|
|
17
25
|
title: z.ZodString;
|
|
18
26
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
19
27
|
cron: z.ZodString;
|
|
@@ -34,8 +42,22 @@ declare const AutomationCreateSchema: z.ZodObject<{
|
|
|
34
42
|
model: string;
|
|
35
43
|
enabled?: boolean | undefined;
|
|
36
44
|
prompt?: string | undefined;
|
|
45
|
+
}>, {
|
|
46
|
+
title: string;
|
|
47
|
+
cron: string;
|
|
48
|
+
timezone: string;
|
|
49
|
+
model: string;
|
|
50
|
+
enabled?: boolean | undefined;
|
|
51
|
+
prompt?: string | undefined;
|
|
52
|
+
}, {
|
|
53
|
+
title: string;
|
|
54
|
+
cron: string;
|
|
55
|
+
timezone: string;
|
|
56
|
+
model: string;
|
|
57
|
+
enabled?: boolean | undefined;
|
|
58
|
+
prompt?: string | undefined;
|
|
37
59
|
}>;
|
|
38
|
-
declare const AutomationPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
60
|
+
declare const AutomationPatchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
39
61
|
title: z.ZodOptional<z.ZodString>;
|
|
40
62
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
41
63
|
cron: z.ZodOptional<z.ZodString>;
|
|
@@ -43,27 +65,39 @@ declare const AutomationPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
|
43
65
|
model: z.ZodOptional<z.ZodString>;
|
|
44
66
|
}, "strict", z.ZodTypeAny, {
|
|
45
67
|
title?: string | undefined;
|
|
46
|
-
enabled?: boolean | undefined;
|
|
47
68
|
cron?: string | undefined;
|
|
48
69
|
timezone?: string | undefined;
|
|
70
|
+
enabled?: boolean | undefined;
|
|
49
71
|
model?: string | undefined;
|
|
50
72
|
}, {
|
|
51
73
|
title?: string | undefined;
|
|
52
|
-
enabled?: boolean | undefined;
|
|
53
74
|
cron?: string | undefined;
|
|
54
75
|
timezone?: string | undefined;
|
|
76
|
+
enabled?: boolean | undefined;
|
|
55
77
|
model?: string | undefined;
|
|
56
78
|
}>, {
|
|
57
79
|
title?: string | undefined;
|
|
58
|
-
enabled?: boolean | undefined;
|
|
59
80
|
cron?: string | undefined;
|
|
60
81
|
timezone?: string | undefined;
|
|
82
|
+
enabled?: boolean | undefined;
|
|
61
83
|
model?: string | undefined;
|
|
62
84
|
}, {
|
|
63
85
|
title?: string | undefined;
|
|
86
|
+
cron?: string | undefined;
|
|
87
|
+
timezone?: string | undefined;
|
|
88
|
+
enabled?: boolean | undefined;
|
|
89
|
+
model?: string | undefined;
|
|
90
|
+
}>, {
|
|
91
|
+
title?: string | undefined;
|
|
92
|
+
cron?: string | undefined;
|
|
93
|
+
timezone?: string | undefined;
|
|
64
94
|
enabled?: boolean | undefined;
|
|
95
|
+
model?: string | undefined;
|
|
96
|
+
}, {
|
|
97
|
+
title?: string | undefined;
|
|
65
98
|
cron?: string | undefined;
|
|
66
99
|
timezone?: string | undefined;
|
|
100
|
+
enabled?: boolean | undefined;
|
|
67
101
|
model?: string | undefined;
|
|
68
102
|
}>;
|
|
69
103
|
declare const PromptUpdateSchema: z.ZodObject<{
|
|
@@ -73,56 +107,31 @@ declare const PromptUpdateSchema: z.ZodObject<{
|
|
|
73
107
|
}, {
|
|
74
108
|
prompt: string;
|
|
75
109
|
}>;
|
|
76
|
-
declare const
|
|
110
|
+
declare const AutomationRunBeginSchema: z.ZodObject<{
|
|
77
111
|
automationId: z.ZodString;
|
|
78
|
-
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
-
status: z.ZodOptional<z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>>;
|
|
80
112
|
trigger: z.ZodEnum<["manual", "scheduled"]>;
|
|
81
113
|
scheduledFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
82
|
-
startedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
83
|
-
completedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
84
|
-
durationMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
85
|
-
inputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
86
|
-
outputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
87
|
-
totalTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
88
114
|
promptSnapshot: z.ZodString;
|
|
89
115
|
modelSnapshot: z.ZodString;
|
|
90
|
-
|
|
116
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
91
117
|
}, "strict", z.ZodTypeAny, {
|
|
92
118
|
automationId: string;
|
|
93
119
|
trigger: "manual" | "scheduled";
|
|
94
120
|
promptSnapshot: string;
|
|
95
121
|
modelSnapshot: string;
|
|
96
|
-
error?: string | null | undefined;
|
|
97
|
-
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
98
|
-
sessionId?: string | null | undefined;
|
|
99
122
|
scheduledFor?: string | null | undefined;
|
|
100
|
-
|
|
101
|
-
completedAt?: string | null | undefined;
|
|
102
|
-
durationMs?: number | null | undefined;
|
|
103
|
-
inputTokens?: number | null | undefined;
|
|
104
|
-
outputTokens?: number | null | undefined;
|
|
105
|
-
totalTokens?: number | null | undefined;
|
|
123
|
+
createdAt?: string | undefined;
|
|
106
124
|
}, {
|
|
107
125
|
automationId: string;
|
|
108
126
|
trigger: "manual" | "scheduled";
|
|
109
127
|
promptSnapshot: string;
|
|
110
128
|
modelSnapshot: string;
|
|
111
|
-
error?: string | null | undefined;
|
|
112
|
-
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
113
|
-
sessionId?: string | null | undefined;
|
|
114
129
|
scheduledFor?: string | null | undefined;
|
|
115
|
-
|
|
116
|
-
completedAt?: string | null | undefined;
|
|
117
|
-
durationMs?: number | null | undefined;
|
|
118
|
-
inputTokens?: number | null | undefined;
|
|
119
|
-
outputTokens?: number | null | undefined;
|
|
120
|
-
totalTokens?: number | null | undefined;
|
|
130
|
+
createdAt?: string | undefined;
|
|
121
131
|
}>;
|
|
122
|
-
declare const
|
|
132
|
+
declare const AutomationRunLifecyclePatchSchema: z.ZodEffects<z.ZodObject<{
|
|
123
133
|
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
124
134
|
status: z.ZodOptional<z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>>;
|
|
125
|
-
scheduledFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
126
135
|
startedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
127
136
|
completedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
128
137
|
durationMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -134,7 +143,6 @@ declare const AutomationRunPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
|
134
143
|
error?: string | null | undefined;
|
|
135
144
|
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
136
145
|
sessionId?: string | null | undefined;
|
|
137
|
-
scheduledFor?: string | null | undefined;
|
|
138
146
|
startedAt?: string | null | undefined;
|
|
139
147
|
completedAt?: string | null | undefined;
|
|
140
148
|
durationMs?: number | null | undefined;
|
|
@@ -145,7 +153,6 @@ declare const AutomationRunPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
|
145
153
|
error?: string | null | undefined;
|
|
146
154
|
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
147
155
|
sessionId?: string | null | undefined;
|
|
148
|
-
scheduledFor?: string | null | undefined;
|
|
149
156
|
startedAt?: string | null | undefined;
|
|
150
157
|
completedAt?: string | null | undefined;
|
|
151
158
|
durationMs?: number | null | undefined;
|
|
@@ -156,7 +163,6 @@ declare const AutomationRunPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
|
156
163
|
error?: string | null | undefined;
|
|
157
164
|
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
158
165
|
sessionId?: string | null | undefined;
|
|
159
|
-
scheduledFor?: string | null | undefined;
|
|
160
166
|
startedAt?: string | null | undefined;
|
|
161
167
|
completedAt?: string | null | undefined;
|
|
162
168
|
durationMs?: number | null | undefined;
|
|
@@ -167,7 +173,6 @@ declare const AutomationRunPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
|
167
173
|
error?: string | null | undefined;
|
|
168
174
|
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
169
175
|
sessionId?: string | null | undefined;
|
|
170
|
-
scheduledFor?: string | null | undefined;
|
|
171
176
|
startedAt?: string | null | undefined;
|
|
172
177
|
completedAt?: string | null | undefined;
|
|
173
178
|
durationMs?: number | null | undefined;
|
|
@@ -184,8 +189,8 @@ declare const IdParamsSchema: z.ZodObject<{
|
|
|
184
189
|
}>;
|
|
185
190
|
type AutomationCreateInput = z.infer<typeof AutomationCreateSchema>;
|
|
186
191
|
type AutomationPatchInput = z.infer<typeof AutomationPatchSchema>;
|
|
187
|
-
type
|
|
188
|
-
type
|
|
192
|
+
type AutomationRunBeginInput = z.infer<typeof AutomationRunBeginSchema>;
|
|
193
|
+
type AutomationRunLifecyclePatchInput = z.infer<typeof AutomationRunLifecyclePatchSchema>;
|
|
189
194
|
|
|
190
195
|
type AutomationRunStatus = "queued" | "running" | "succeeded" | "failed" | "cancelled";
|
|
191
196
|
type AutomationRunTrigger = "manual" | "scheduled";
|
|
@@ -234,26 +239,17 @@ interface AutomationRun {
|
|
|
234
239
|
createdAt: string;
|
|
235
240
|
updatedAt: string;
|
|
236
241
|
}
|
|
237
|
-
interface
|
|
242
|
+
interface AutomationRunBegin {
|
|
238
243
|
automationId: string;
|
|
239
|
-
sessionId?: string | null;
|
|
240
|
-
status?: AutomationRunStatus;
|
|
241
244
|
trigger: AutomationRunTrigger;
|
|
242
245
|
scheduledFor?: string | null;
|
|
243
|
-
startedAt?: string | null;
|
|
244
|
-
completedAt?: string | null;
|
|
245
|
-
durationMs?: number | null;
|
|
246
|
-
inputTokens?: number | null;
|
|
247
|
-
outputTokens?: number | null;
|
|
248
|
-
totalTokens?: number | null;
|
|
249
246
|
promptSnapshot: string;
|
|
250
247
|
modelSnapshot: string;
|
|
251
|
-
|
|
248
|
+
createdAt?: string;
|
|
252
249
|
}
|
|
253
|
-
interface
|
|
250
|
+
interface AutomationRunLifecyclePatch {
|
|
254
251
|
sessionId?: string | null;
|
|
255
252
|
status?: AutomationRunStatus;
|
|
256
|
-
scheduledFor?: string | null;
|
|
257
253
|
startedAt?: string | null;
|
|
258
254
|
completedAt?: string | null;
|
|
259
255
|
durationMs?: number | null;
|
|
@@ -263,4 +259,44 @@ interface AutomationRunPatch {
|
|
|
263
259
|
error?: string | null;
|
|
264
260
|
}
|
|
265
261
|
|
|
266
|
-
|
|
262
|
+
declare const AUTOMATION_SCHEDULE_ERRORS: {
|
|
263
|
+
readonly INVALID_CRON: "Invalid cron schedule. Use exactly five fields, for example 0 9 * * *.";
|
|
264
|
+
readonly INVALID_TIMEZONE: "Invalid timezone. Use a valid IANA timezone, for example UTC or America/New_York.";
|
|
265
|
+
};
|
|
266
|
+
type AutomationScheduleSkipReason = "disabled" | "invalid-cron" | "invalid-timezone" | "not-current-minute" | "duplicate-scheduled-run" | "active-run";
|
|
267
|
+
type AutomationScheduleDueReason = "current-minute-matched";
|
|
268
|
+
interface AutomationScheduleValidationResult {
|
|
269
|
+
ok: boolean;
|
|
270
|
+
errors: Partial<Record<"cron" | "timezone", string>>;
|
|
271
|
+
}
|
|
272
|
+
interface AutomationScheduleDueDecision {
|
|
273
|
+
kind: "due";
|
|
274
|
+
automation: Automation;
|
|
275
|
+
automationId: string;
|
|
276
|
+
scheduledFor: string;
|
|
277
|
+
reason: AutomationScheduleDueReason;
|
|
278
|
+
}
|
|
279
|
+
interface AutomationScheduleSkipDecision {
|
|
280
|
+
kind: "skip";
|
|
281
|
+
automation: Automation;
|
|
282
|
+
automationId: string;
|
|
283
|
+
scheduledFor: string | null;
|
|
284
|
+
reason: AutomationScheduleSkipReason;
|
|
285
|
+
message: string;
|
|
286
|
+
}
|
|
287
|
+
type AutomationScheduleDecision = AutomationScheduleDueDecision | AutomationScheduleSkipDecision;
|
|
288
|
+
interface EvaluateAutomationScheduleInput {
|
|
289
|
+
automations: readonly Automation[];
|
|
290
|
+
runs: readonly AutomationRun[];
|
|
291
|
+
now: Date;
|
|
292
|
+
}
|
|
293
|
+
interface EvaluateAutomationScheduleResult {
|
|
294
|
+
decisions: AutomationScheduleDecision[];
|
|
295
|
+
due: AutomationScheduleDueDecision[];
|
|
296
|
+
}
|
|
297
|
+
declare function validateAutomationSchedule(cron: string, timezone: string): AutomationScheduleValidationResult;
|
|
298
|
+
declare function isValidFiveFieldCron(cron: string): boolean;
|
|
299
|
+
declare function isValidIanaTimeZone(timezone: string): boolean;
|
|
300
|
+
declare function evaluateAutomationSchedule(input: EvaluateAutomationScheduleInput): EvaluateAutomationScheduleResult;
|
|
301
|
+
|
|
302
|
+
export { AUTOMATION_SCHEDULE_ERRORS, type Automation, type AutomationCreate, type AutomationCreateInput, AutomationCreateSchema, type AutomationPatch, type AutomationPatchInput, AutomationPatchSchema, type AutomationRun, type AutomationRunBegin, type AutomationRunBeginInput, AutomationRunBeginSchema, type AutomationRunLifecyclePatch, type AutomationRunLifecyclePatchInput, AutomationRunLifecyclePatchSchema, type AutomationRunStatus, AutomationRunStatusSchema, type AutomationRunTrigger, AutomationRunTriggerSchema, type AutomationScheduleDecision, type AutomationScheduleDueDecision, type AutomationScheduleDueReason, type AutomationScheduleSkipDecision, type AutomationScheduleSkipReason, type AutomationScheduleValidationResult, BORING_AUTOMATION_ERROR_CODES, BORING_AUTOMATION_PLUGIN_ID, BORING_AUTOMATION_PLUGIN_LABEL, BORING_AUTOMATION_ROUTE_PREFIX, type BoringAutomationErrorCode, type EvaluateAutomationScheduleInput, type EvaluateAutomationScheduleResult, IdParamsSchema, PromptUpdateSchema, evaluateAutomationSchedule, isValidFiveFieldCron, isValidIanaTimeZone, validateAutomationSchedule };
|
package/dist/shared/index.js
CHANGED
|
@@ -6,12 +6,110 @@ var BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
|
6
6
|
// src/shared/error-codes.ts
|
|
7
7
|
var BORING_AUTOMATION_ERROR_CODES = {
|
|
8
8
|
INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY",
|
|
9
|
+
INVALID_CRON: "BORING_AUTOMATION_INVALID_CRON",
|
|
10
|
+
INVALID_TIMEZONE: "BORING_AUTOMATION_INVALID_TIMEZONE",
|
|
11
|
+
INVALID_MODEL: "BORING_AUTOMATION_INVALID_MODEL",
|
|
9
12
|
AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND",
|
|
10
|
-
RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND"
|
|
13
|
+
RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND",
|
|
14
|
+
RUN_ALREADY_ACTIVE: "BORING_AUTOMATION_RUN_ALREADY_ACTIVE",
|
|
15
|
+
RUN_ALREADY_RECORDED: "BORING_AUTOMATION_RUN_ALREADY_RECORDED",
|
|
16
|
+
RUN_EXECUTOR_UNAVAILABLE: "BORING_AUTOMATION_RUN_EXECUTOR_UNAVAILABLE",
|
|
17
|
+
TRIGGER_FORBIDDEN: "BORING_AUTOMATION_TRIGGER_FORBIDDEN",
|
|
18
|
+
RUN_FAILED: "BORING_AUTOMATION_RUN_FAILED"
|
|
11
19
|
};
|
|
12
20
|
|
|
13
21
|
// src/shared/schema.ts
|
|
14
22
|
import { z } from "zod";
|
|
23
|
+
|
|
24
|
+
// src/shared/schedule.ts
|
|
25
|
+
import { Cron } from "croner";
|
|
26
|
+
var AUTOMATION_SCHEDULE_ERRORS = {
|
|
27
|
+
INVALID_CRON: "Invalid cron schedule. Use exactly five fields, for example 0 9 * * *.",
|
|
28
|
+
INVALID_TIMEZONE: "Invalid timezone. Use a valid IANA timezone, for example UTC or America/New_York."
|
|
29
|
+
};
|
|
30
|
+
function validateAutomationSchedule(cron, timezone) {
|
|
31
|
+
const errors = {};
|
|
32
|
+
if (!isValidFiveFieldCron(cron)) errors.cron = AUTOMATION_SCHEDULE_ERRORS.INVALID_CRON;
|
|
33
|
+
if (!isValidIanaTimeZone(timezone)) errors.timezone = AUTOMATION_SCHEDULE_ERRORS.INVALID_TIMEZONE;
|
|
34
|
+
return { ok: Object.keys(errors).length === 0, errors };
|
|
35
|
+
}
|
|
36
|
+
function isValidFiveFieldCron(cron) {
|
|
37
|
+
const normalized = normalizeSpace(cron);
|
|
38
|
+
if (!normalized || normalized.split(" ").length !== 5) return false;
|
|
39
|
+
try {
|
|
40
|
+
new Cron(normalized);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isValidIanaTimeZone(timezone) {
|
|
47
|
+
const normalized = timezone.trim();
|
|
48
|
+
if (!normalized) return false;
|
|
49
|
+
try {
|
|
50
|
+
const resolved = new Intl.DateTimeFormat("en-US", { timeZone: normalized }).resolvedOptions().timeZone;
|
|
51
|
+
return resolved === "UTC" || resolved.includes("/");
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function evaluateAutomationSchedule(input) {
|
|
57
|
+
const now = new Date(input.now);
|
|
58
|
+
const scheduledFor = floorToMinute(now).toISOString();
|
|
59
|
+
const runs = [...input.runs];
|
|
60
|
+
const decisions = input.automations.map((automation) => {
|
|
61
|
+
const validation = validateAutomationSchedule(automation.cron, automation.timezone);
|
|
62
|
+
if (validation.errors.cron) return skip(automation, null, "invalid-cron", validation.errors.cron);
|
|
63
|
+
if (validation.errors.timezone) return skip(automation, null, "invalid-timezone", validation.errors.timezone);
|
|
64
|
+
if (!automation.enabled) return skip(automation, null, "disabled", "Automation is disabled.");
|
|
65
|
+
const cron = new Cron(normalizeSpace(automation.cron), { timezone: automation.timezone.trim() });
|
|
66
|
+
if (!cron.match(new Date(scheduledFor))) {
|
|
67
|
+
return skip(automation, null, "not-current-minute", "Automation is not scheduled for the current minute.");
|
|
68
|
+
}
|
|
69
|
+
if (hasDuplicateScheduledRun(runs, automation.id, scheduledFor)) {
|
|
70
|
+
return skip(automation, scheduledFor, "duplicate-scheduled-run", "A run already exists for this scheduled minute.");
|
|
71
|
+
}
|
|
72
|
+
if (hasActiveRun(runs, automation.id)) {
|
|
73
|
+
return skip(automation, scheduledFor, "active-run", "Automation already has a queued or running run.");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
kind: "due",
|
|
77
|
+
automation,
|
|
78
|
+
automationId: automation.id,
|
|
79
|
+
scheduledFor,
|
|
80
|
+
reason: "current-minute-matched"
|
|
81
|
+
};
|
|
82
|
+
}).sort(compareScheduleDecisions);
|
|
83
|
+
return { decisions, due: decisions.filter((decision) => decision.kind === "due") };
|
|
84
|
+
}
|
|
85
|
+
function skip(automation, scheduledFor, reason, message) {
|
|
86
|
+
return { kind: "skip", automation, automationId: automation.id, scheduledFor, reason, message };
|
|
87
|
+
}
|
|
88
|
+
function hasDuplicateScheduledRun(runs, automationId, scheduledFor) {
|
|
89
|
+
return runs.some((run) => run.automationId === automationId && run.trigger === "scheduled" && run.scheduledFor === scheduledFor);
|
|
90
|
+
}
|
|
91
|
+
function hasActiveRun(runs, automationId) {
|
|
92
|
+
return runs.some((run) => run.automationId === automationId && (run.status === "queued" || run.status === "running"));
|
|
93
|
+
}
|
|
94
|
+
function floorToMinute(date) {
|
|
95
|
+
const next = new Date(date);
|
|
96
|
+
next.setUTCSeconds(0, 0);
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
function normalizeSpace(value) {
|
|
100
|
+
return value.trim().replace(/\s+/g, " ");
|
|
101
|
+
}
|
|
102
|
+
function compareScheduleDecisions(a, b) {
|
|
103
|
+
return compareNullableString(a.scheduledFor, b.scheduledFor) || a.automationId.localeCompare(b.automationId);
|
|
104
|
+
}
|
|
105
|
+
function compareNullableString(a, b) {
|
|
106
|
+
if (a === b) return 0;
|
|
107
|
+
if (a === null) return 1;
|
|
108
|
+
if (b === null) return -1;
|
|
109
|
+
return a.localeCompare(b);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/shared/schema.ts
|
|
15
113
|
var nonEmptyString = z.string().trim().min(1);
|
|
16
114
|
var isoString = z.string().datetime({ offset: true });
|
|
17
115
|
var nonNegativeInteger = z.number().int().nonnegative();
|
|
@@ -24,37 +122,32 @@ var AutomationCreateSchema = z.object({
|
|
|
24
122
|
timezone: nonEmptyString,
|
|
25
123
|
model: nonEmptyString,
|
|
26
124
|
prompt: z.string().optional()
|
|
27
|
-
}).strict()
|
|
125
|
+
}).strict().superRefine((value, ctx) => {
|
|
126
|
+
addScheduleIssues(ctx, value);
|
|
127
|
+
});
|
|
28
128
|
var AutomationPatchSchema = z.object({
|
|
29
129
|
title: nonEmptyString.optional(),
|
|
30
130
|
enabled: z.boolean().optional(),
|
|
31
131
|
cron: nonEmptyString.optional(),
|
|
32
132
|
timezone: nonEmptyString.optional(),
|
|
33
133
|
model: nonEmptyString.optional()
|
|
34
|
-
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided")
|
|
134
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided").superRefine((value, ctx) => {
|
|
135
|
+
addScheduleIssues(ctx, value);
|
|
136
|
+
});
|
|
35
137
|
var PromptUpdateSchema = z.object({
|
|
36
138
|
prompt: z.string()
|
|
37
139
|
}).strict();
|
|
38
|
-
var
|
|
140
|
+
var AutomationRunBeginSchema = z.object({
|
|
39
141
|
automationId: nonEmptyString,
|
|
40
|
-
sessionId: nonEmptyString.nullable().optional(),
|
|
41
|
-
status: AutomationRunStatusSchema.optional(),
|
|
42
142
|
trigger: AutomationRunTriggerSchema,
|
|
43
143
|
scheduledFor: isoString.nullable().optional(),
|
|
44
|
-
startedAt: isoString.nullable().optional(),
|
|
45
|
-
completedAt: isoString.nullable().optional(),
|
|
46
|
-
durationMs: nonNegativeInteger.nullable().optional(),
|
|
47
|
-
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
48
|
-
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
49
|
-
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
50
144
|
promptSnapshot: z.string(),
|
|
51
145
|
modelSnapshot: nonEmptyString,
|
|
52
|
-
|
|
146
|
+
createdAt: isoString.optional()
|
|
53
147
|
}).strict();
|
|
54
|
-
var
|
|
148
|
+
var AutomationRunLifecyclePatchSchema = z.object({
|
|
55
149
|
sessionId: nonEmptyString.nullable().optional(),
|
|
56
150
|
status: AutomationRunStatusSchema.optional(),
|
|
57
|
-
scheduledFor: isoString.nullable().optional(),
|
|
58
151
|
startedAt: isoString.nullable().optional(),
|
|
59
152
|
completedAt: isoString.nullable().optional(),
|
|
60
153
|
durationMs: nonNegativeInteger.nullable().optional(),
|
|
@@ -64,11 +157,20 @@ var AutomationRunPatchSchema = z.object({
|
|
|
64
157
|
error: z.string().nullable().optional()
|
|
65
158
|
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
66
159
|
var IdParamsSchema = z.object({ id: nonEmptyString });
|
|
160
|
+
function addScheduleIssues(ctx, value) {
|
|
161
|
+
if (value.cron !== void 0 && !isValidFiveFieldCron(value.cron)) {
|
|
162
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["cron"], message: AUTOMATION_SCHEDULE_ERRORS.INVALID_CRON });
|
|
163
|
+
}
|
|
164
|
+
if (value.timezone !== void 0 && !isValidIanaTimeZone(value.timezone)) {
|
|
165
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["timezone"], message: AUTOMATION_SCHEDULE_ERRORS.INVALID_TIMEZONE });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
67
168
|
export {
|
|
169
|
+
AUTOMATION_SCHEDULE_ERRORS,
|
|
68
170
|
AutomationCreateSchema,
|
|
69
171
|
AutomationPatchSchema,
|
|
70
|
-
|
|
71
|
-
|
|
172
|
+
AutomationRunBeginSchema,
|
|
173
|
+
AutomationRunLifecyclePatchSchema,
|
|
72
174
|
AutomationRunStatusSchema,
|
|
73
175
|
AutomationRunTriggerSchema,
|
|
74
176
|
BORING_AUTOMATION_ERROR_CODES,
|
|
@@ -76,5 +178,9 @@ export {
|
|
|
76
178
|
BORING_AUTOMATION_PLUGIN_LABEL,
|
|
77
179
|
BORING_AUTOMATION_ROUTE_PREFIX,
|
|
78
180
|
IdParamsSchema,
|
|
79
|
-
PromptUpdateSchema
|
|
181
|
+
PromptUpdateSchema,
|
|
182
|
+
evaluateAutomationSchedule,
|
|
183
|
+
isValidFiveFieldCron,
|
|
184
|
+
isValidIanaTimeZone,
|
|
185
|
+
validateAutomationSchedule
|
|
80
186
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-automation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,26 +39,20 @@
|
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"sideEffects": false,
|
|
42
|
-
"scripts": {
|
|
43
|
-
"build": "tsup",
|
|
44
|
-
"typecheck": "tsc --noEmit",
|
|
45
|
-
"test": "vitest run --no-file-parallelism",
|
|
46
|
-
"lint": "pnpm run typecheck",
|
|
47
|
-
"clean": "rm -rf dist .tsbuildinfo"
|
|
48
|
-
},
|
|
49
42
|
"peerDependencies": {
|
|
50
|
-
"@hachej/boring-workspace": "workspace:*",
|
|
51
43
|
"fastify": "^5.9.0",
|
|
52
44
|
"react": "^18.0.0 || ^19.0.0",
|
|
53
|
-
"react-dom": "^18.0.0 || ^19.0.0"
|
|
45
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
46
|
+
"@hachej/boring-workspace": "0.1.80"
|
|
54
47
|
},
|
|
55
48
|
"dependencies": {
|
|
56
|
-
"
|
|
49
|
+
"croner": "^10.0.1",
|
|
57
50
|
"lucide-react": "^1.21.0",
|
|
58
|
-
"
|
|
51
|
+
"postgres": "^3.4.7",
|
|
52
|
+
"zod": "^3.23.0",
|
|
53
|
+
"@hachej/boring-ui-kit": "0.1.80"
|
|
59
54
|
},
|
|
60
55
|
"devDependencies": {
|
|
61
|
-
"@hachej/boring-workspace": "workspace:*",
|
|
62
56
|
"@testing-library/jest-dom": "^6.9.1",
|
|
63
57
|
"@testing-library/react": "^16.3.0",
|
|
64
58
|
"@types/node": "^22.20.0",
|
|
@@ -71,11 +65,19 @@
|
|
|
71
65
|
"react-dom": "^19.2.7",
|
|
72
66
|
"tsup": "^8.4.0",
|
|
73
67
|
"typescript": "~6.0.3",
|
|
74
|
-
"vitest": "^4.1.9"
|
|
68
|
+
"vitest": "^4.1.9",
|
|
69
|
+
"@hachej/boring-workspace": "0.1.80"
|
|
75
70
|
},
|
|
76
71
|
"peerDependenciesMeta": {
|
|
77
72
|
"fastify": {
|
|
78
73
|
"optional": true
|
|
79
74
|
}
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsup",
|
|
78
|
+
"typecheck": "tsc --noEmit",
|
|
79
|
+
"test": "vitest run --no-file-parallelism",
|
|
80
|
+
"lint": "pnpm run typecheck",
|
|
81
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
80
82
|
}
|
|
81
|
-
}
|
|
83
|
+
}
|